architecture-patterns
Architecture Patterns Skill
Architectural design patterns, decision frameworks, and system design principles.
When This Skill Activates
- Designing system architecture
- Writing Architecture Decision Records (ADRs)
- Evaluating design patterns
- Making architectural tradeoffs
- System design questions
- Keywords: "architecture", "design", "pattern", "adr", "system design", "scalability"
Architecture Decision Records (ADRs)
What is an ADR?
An ADR documents an architectural decision - the context, the decision made, and the consequences.
When to Write an ADR
Write an ADR for decisions that:
- Are hard to reverse
- Impact multiple teams
- Involve significant tradeoffs
- Set precedents for future work
Examples:
- ✅ "We chose PostgreSQL over MongoDB"
- ✅ "We split the monolith into microservices"
- ✅ "We adopted event-driven architecture"
- ❌ "We renamed a function" (too trivial)
- ❌ "We fixed a bug" (not architectural)
ADR Template
# ADR-### [Short Title]
**Date**: YYYY-MM-DD
**Status**: [Proposed | Accepted | Deprecated | Superseded]
**Deciders**: [Names/Roles]
## Context
What is the issue we're trying to solve? What are the constraints?
Example:
> Our monolithic application has grown to 200K lines of code.
> Deploy times are 45+ minutes, and teams are blocked on each other.
> We need to improve deployment speed and team autonomy.
## Decision
What did we decide to do?
Example:
> We will split the monolith into domain-driven microservices,
> starting with the user service and order service.
## Alternatives Considered
What other options did we evaluate?
### Option 1: Keep the monolith
**Pros**: No migration cost, simpler deployment
**Cons**: Deploy times won't improve, team blocking continues
### Option 2: Modular monolith
**Pros**: Better than status quo, no network calls
**Cons**: Still single deployment unit, doesn't solve deploy time
### Option 3: Microservices (chosen)
**Pros**: Independent deploys, team autonomy, scalability
**Cons**: Complexity, network calls, distributed system challenges
## Consequences
### Positive
- Deploy times drop from 45min to 5min per service
- Teams can deploy independently
- Services can scale independently
### Negative
- Need service mesh (added complexity)
- Distributed tracing required
- Data consistency challenges
### Neutral
- Migration will take 6 months
- Need to train team on distributed systems
## Implementation Notes
- Phase 1: Extract user service (Month 1-2)
- Phase 2: Extract order service (Month 3-4)
- Phase 3: Extract payment service (Month 5-6)
- Use API gateway for routing
- Adopt Kubernetes for orchestration
## References
- [Martin Fowler - Microservices](https://martinfowler.com/articles/microservices.html)
- Internal: `docs/microservices-migration-plan.md`
---
**Supersedes**: [ADR-005] if this replaces an earlier decision
**Superseded by**: [ADR-015] if a later decision overrides this
ADR Lifecycle
- Proposed: Draft for review
- Accepted: Team approved, implementing
- Deprecated: No longer recommended, but not replaced
- Superseded: Replaced by a newer ADR
Common Architecture Patterns
1. Layered (N-Tier) Architecture
Structure:
┌─────────────────────────┐
│ Presentation Layer │ (UI, Controllers)
├─────────────────────────┤
│ Business Logic │ (Services, Domain)
├─────────────────────────┤
│ Data Access Layer │ (Repositories, ORM)
├─────────────────────────┤
│ Database │ (PostgreSQL, MySQL)
└─────────────────────────┘
When to use: Traditional web applications, CRUD-heavy systems
Pros:
- Simple to understand and implement
- Clear separation of concerns
- Easy to test each layer independently
Cons:
- Can become monolithic
- Changes ripple through layers
- Performance overhead from layer boundaries
Example use case: E-commerce website, internal business tools
2. Microservices Architecture
Structure:
┌────────────┐ ┌────────────┐ ┌────────────┐
│ User │ │ Order │ │ Payment │
│ Service │ │ Service │ │ Service │
└────────────┘ └────────────┘ └────────────┘
│ │ │
└───────────────┴───────────────┘
│
┌──────────────┐
│ API Gateway │
└──────────────┘
When to use: Large teams, independent deployment needs, high scalability requirements
Pros:
- Independent deployment and scaling
- Team autonomy
- Technology diversity possible
- Fault isolation
Cons:
- Distributed system complexity
- Network latency
- Data consistency challenges
- Higher operational overhead
Example use case: Netflix, Amazon, large-scale SaaS platforms
3. Event-Driven Architecture
Structure:
┌────────────┐ ┌────────────┐
│ Service A │───► Event Bus ───►│ Service B │
└────────────┘ (Kafka) └────────────┘
│
▼
┌────────────┐
│ Service C │
└────────────┘
When to use: Real-time systems, async workflows, event sourcing
Pros:
- Loose coupling between services
- Highly scalable
- Natural fit for real-time/streaming
- Easy to add new consumers
Cons:
- Debugging is harder (distributed traces)
- Event ordering challenges
- At-least-once/exactly-once semantics complexity
Example use case: Stock trading platforms, IoT systems, real-time analytics
4. Hexagonal Architecture (Ports & Adapters)
Structure:
┌──────────────────────┐
│ Domain Logic │
│ (Business Rules) │
└──────────────────────┘
▲ ▲
│ │
┌─────┘ └─────┐
│ │
┌────▼────┐ ┌────▼────┐
│ HTTP │ │Database │
│ Adapter │ │ Adapter │
└─────────┘ └─────────┘
When to use: Domain-driven design, testability is critical
Pros:
- Business logic isolated from infrastructure
- Easy to test (mock adapters)
- Easy to swap implementations (e.g., swap database)
Cons:
- More initial setup
- Can be over-engineering for simple CRUD
Example use case: Banking systems, healthcare applications (domain-heavy)
5. Serverless Architecture
Structure:
API Gateway → Lambda → DynamoDB
→ Lambda → S3
→ Lambda → SQS
When to use: Variable/unpredictable load, event-driven tasks
Pros:
- No server management
- Pay-per-use pricing
- Auto-scaling
- Fast to deploy
Cons:
- Cold start latency
- Vendor lock-in
- Debugging is harder
- Limited execution time
Example use case: Image processing, webhooks, scheduled jobs
Design Patterns (Gang of Four)
Creational Patterns
Singleton
Purpose: Ensure only one instance exists
class DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.connection = create_connection()
return cls._instance
# Usage
db1 = DatabaseConnection() # Creates instance
db2 = DatabaseConnection() # Returns same instance
assert db1 is db2 # True
When to use: Shared resources (DB connection, config, cache)
Caution: Can make testing difficult (global state)
Factory Pattern
Purpose: Create objects without specifying exact class
class PaymentProcessorFactory:
@staticmethod
def create(payment_type: str):
if payment_type == "credit_card":
return CreditCardProcessor()
elif payment_type == "paypal":
return PayPalProcessor()
elif payment_type == "crypto":
return CryptoProcessor()
raise ValueError(f"Unknown payment type: {payment_type}")
# Usage
processor = PaymentProcessorFactory.create("credit_card")
processor.process(amount=100)
When to use: Object creation logic is complex or conditional
Structural Patterns
Adapter Pattern
Purpose: Make incompatible interfaces work together
# Legacy system
class OldLogger:
def log_message(self, msg):
print(f"[OLD] {msg}")
# New interface
class Logger:
def log(self, level, message):
pass
# Adapter
class OldLoggerAdapter(Logger):
def __init__(self, old_logger):
self.old_logger = old_logger
def log(self, level, message):
self.old_logger.log_message(f"{level}: {message}")
# Usage
old = OldLogger()
adapter = OldLoggerAdapter(old)
adapter.log("INFO", "System started") # Works with new interface!
When to use: Integrating legacy code, third-party libraries
Decorator Pattern
Purpose: Add behavior to objects dynamically
# Base
class Coffee:
def cost(self):
return 2.00
# Decorators
class MilkDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self):
return self.coffee.cost() + 0.50
class SugarDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self):
return self.coffee.cost() + 0.25
# Usage
coffee = Coffee()
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee)
print(coffee.cost()) # 2.75
When to use: Add responsibilities without subclassing
Behavioral Patterns
Strategy Pattern
Purpose: Select algorithm at runtime
from abc import ABC, abstractmethod
class TrainingStrategy(ABC):
@abstractmethod
def train(self, model, data):
pass
class LoRAStrategy(TrainingStrategy):
def train(self, model, data):
# LoRA-specific training
pass
class DPOStrategy(TrainingStrategy):
def train(self, model, data):
# DPO-specific training
pass
class Trainer:
def __init__(self, strategy: TrainingStrategy):
self.strategy = strategy
def run(self, model, data):
self.strategy.train(model, data)
# Usage
trainer = Trainer(LoRAStrategy())
trainer.run(model, data)
# Switch strategy
trainer.strategy = DPOStrategy()
trainer.run(model, data)
When to use: Multiple algorithms, select at runtime
Observer Pattern
Purpose: Notify dependents when state changes
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def notify(self, event):
for observer in self._observers:
observer.update(event)
class Logger:
def update(self, event):
print(f"[LOG] {event}")
class EmailNotifier:
def update(self, event):
print(f"[EMAIL] Sending alert for: {event}")
# Usage
order_system = Subject()
order_system.attach(Logger())
order_system.attach(EmailNotifier())
order_system.notify("Order placed") # Both observers notified
When to use: Event systems, publish-subscribe patterns
System Design Principles
SOLID Principles
S - Single Responsibility
Rule: A class should have ONE reason to change
# ❌ BAD: Multiple responsibilities
class User:
def save_to_database(self): ...
def send_email(self): ...
def generate_report(self): ...
# ✅ GOOD: Single responsibility
class User:
pass
class UserRepository:
def save(self, user): ...
class EmailService:
def send_welcome_email(self, user): ...
class ReportGenerator:
def generate_user_report(self, user): ...
O - Open/Closed
Rule: Open for extension, closed for modification
# ❌ BAD: Must modify class to add new shapes
class AreaCalculator:
def calculate(self, shapes):
total = 0
for shape in shapes:
if shape.type == "circle":
total += 3.14 * shape.radius ** 2
elif shape.type == "square":
total += shape.side ** 2
return total
# ✅ GOOD: Extend via new classes
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Circle(Shape):
def area(self):
return 3.14 * self.radius ** 2
class Square(Shape):
def area(self):
return self.side ** 2
class AreaCalculator:
def calculate(self, shapes):
return sum(shape.area() for shape in shapes)
L - Liskov Substitution
Rule: Subtypes must be substitutable for base types
# ❌ BAD: Violates LSP
class Bird:
def fly(self): pass
class Penguin(Bird): # Penguins can't fly!
def fly(self):
raise NotImplementedError("Penguins can't fly")
# ✅ GOOD: Proper hierarchy
class Bird:
pass
class FlyingBird(Bird):
def fly(self): pass
class Sparrow(FlyingBird):
def fly(self): ...
class Penguin(Bird):
def swim(self): ...
I - Interface Segregation
Rule: Many specific interfaces > one general interface
# ❌ BAD: Fat interface
class Worker:
def work(self): pass
def eat(self): pass
class Robot(Worker): # Robots don't eat!
def eat(self):
raise NotImplementedError()
# ✅ GOOD: Segregated interfaces
class Workable:
def work(self): pass
class Eatable:
def eat(self): pass
class Human(Workable, Eatable):
def work(self): ...
def eat(self): ...
class Robot(Workable):
def work(self): ...
D - Dependency Inversion
Rule: Depend on abstractions, not concretions
# ❌ BAD: Depends on concrete class
class EmailService:
pass
class NotificationManager:
def __init__(self):
self.email = EmailService() # Hard dependency!
# ✅ GOOD: Depends on abstraction
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, message): pass
class EmailNotifier(Notifier):
def send(self, message): ...
class SMSNotifier(Notifier):
def send(self, message): ...
class NotificationManager:
def __init__(self, notifier: Notifier):
self.notifier = notifier # Depends on abstraction
Other Key Principles
DRY (Don't Repeat Yourself)
Rule: Every piece of knowledge should have a single representation
# ❌ BAD: Duplicated validation
def create_user(email):
if "@" not in email:
raise ValueError("Invalid email")
...
def update_user(email):
if "@" not in email: # Duplicated!
raise ValueError("Invalid email")
...
# ✅ GOOD: Single source of truth
def validate_email(email):
if "@" not in email:
raise ValueError("Invalid email")
def create_user(email):
validate_email(email)
...
def update_user(email):
validate_email(email)
...
KISS (Keep It Simple, Stupid)
Rule: Simplest solution that works
# ❌ BAD: Over-engineered
class AbstractFactoryBuilderSingletonProxy:
def create_instance_with_dependency_injection():
...
# ✅ GOOD: Simple and clear
def create_user(name, email):
return User(name=name, email=email)
YAGNI (You Aren't Gonna Need It)
Rule: Don't add functionality until needed
# ❌ BAD: Adding features "just in case"
class User:
def export_to_json(self): ...
def export_to_xml(self): ... # Do we need XML?
def export_to_yaml(self): ... # Do we need YAML?
def export_to_csv(self): ... # Do we need CSV?
# ✅ GOOD: Only what's needed now
class User:
def export_to_json(self): # Only JSON needed right now
...
Tradeoff Analysis Framework
Performance vs. Simplicity
| Approach | Performance | Simplicity | When to Use |
|---|---|---|---|
| In-memory cache | ⭐⭐⭐ | ⭐⭐ | Hot data, read-heavy |
| Database query | ⭐ | ⭐⭐⭐ | Simple CRUD, occasional access |
| Redis cache | ⭐⭐ | ⭐ | Distributed caching needed |
Consistency vs. Availability (CAP Theorem)
Rule: In a distributed system, you can have at most 2 of 3:
- Consistency: All nodes see same data
- Availability: Every request gets a response
- Partition tolerance: System works despite network splits
Choices:
- CP: Consistency + Partition Tolerance (e.g., MongoDB, HBase)
- AP: Availability + Partition Tolerance (e.g., Cassandra, DynamoDB)
- CA: Not possible in distributed systems (network partitions happen!)
Coupling vs. Cohesion
Goal: Low coupling, high cohesion
# ❌ BAD: High coupling, low cohesion
class OrderProcessor:
def __init__(self, db, email, payment, inventory):
self.db = db
self.email = email
self.payment = payment
self.inventory = inventory # Coupled to 4 systems!
# ✅ GOOD: Low coupling, high cohesion
class OrderProcessor:
def __init__(self, order_repository):
self.repository = order_repository
class PaymentService:
def process(self, order): ...
class InventoryService:
def reserve(self, items): ...
Integration with [PROJECT_NAME]
[PROJECT_NAME] architectural principles:
- Patterns: Hexagonal architecture, event-driven for async tasks
- ADRs: Document all major decisions in
docs/adr/ - Principles: SOLID, DRY, KISS, YAGNI
- Design reviews: All major architecture changes reviewed before implementation
- Tradeoff documentation: Explain WHY we chose an approach
Additional Resources
Books:
- "Design Patterns" by Gang of Four
- "Clean Architecture" by Robert Martin
- "Software Architecture: The Hard Parts" by Neal Ford
- "Building Microservices" by Sam Newman
Websites:
Version: 1.0.0 Type: Knowledge skill (no scripts) See Also: documentation-guide (for ADR formatting), python-standards, code-review
More from akaszubski/anyclaude-local
testing-guide
Complete testing methodology - TDD, progression tracking, regression prevention, and test patterns
1observability
Logging, debugging, profiling, and performance monitoring for development. Use when adding logging, debugging issues, profiling performance, or instrumenting code for observability.
1git-workflow
Git best practices, commit conventions, branching strategies, and pull request workflows. Use when working with git operations, commits, branches, or PRs.
1python-standards
Python code quality standards (PEP 8, type hints, docstrings). Use when writing Python code.
1github-workflow
GitHub-first workflow - Issues, PRs, milestones, auto-tracking for solo developer productivity
1code-review
This skill should be used when reviewing code or preparing code for review. It provides guidelines for what to look for in reviews, how to write constructive feedback, and standards for review comments.
1