plugins/autonomous-dev/skills/architecture-patterns/SKILL.md
This skill should be used when designing system architecture, making architectural decisions, or evaluating design patterns. It provides guidance on common patterns, ADR templates, design principles, and tradeoff analysis.
npx skillsauth add akaszubski/anyclaude-local architecture-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Architectural design patterns, decision frameworks, and system design principles.
An ADR documents an architectural decision - the context, the decision made, and the consequences.
Write an ADR for decisions that:
Examples:
# 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
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:
Cons:
Example use case: E-commerce website, internal business tools
Structure:
┌────────────┐ ┌────────────┐ ┌────────────┐
│ User │ │ Order │ │ Payment │
│ Service │ │ Service │ │ Service │
└────────────┘ └────────────┘ └────────────┘
│ │ │
└───────────────┴───────────────┘
│
┌──────────────┐
│ API Gateway │
└──────────────┘
When to use: Large teams, independent deployment needs, high scalability requirements
Pros:
Cons:
Example use case: Netflix, Amazon, large-scale SaaS platforms
Structure:
┌────────────┐ ┌────────────┐
│ Service A │───► Event Bus ───►│ Service B │
└────────────┘ (Kafka) └────────────┘
│
▼
┌────────────┐
│ Service C │
└────────────┘
When to use: Real-time systems, async workflows, event sourcing
Pros:
Cons:
Example use case: Stock trading platforms, IoT systems, real-time analytics
Structure:
┌──────────────────────┐
│ Domain Logic │
│ (Business Rules) │
└──────────────────────┘
▲ ▲
│ │
┌─────┘ └─────┐
│ │
┌────▼────┐ ┌────▼────┐
│ HTTP │ │Database │
│ Adapter │ │ Adapter │
└─────────┘ └─────────┘
When to use: Domain-driven design, testability is critical
Pros:
Cons:
Example use case: Banking systems, healthcare applications (domain-heavy)
Structure:
API Gateway → Lambda → DynamoDB
→ Lambda → S3
→ Lambda → SQS
When to use: Variable/unpredictable load, event-driven tasks
Pros:
Cons:
Example use case: Image processing, webhooks, scheduled jobs
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)
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
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
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
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
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
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): ...
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)
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): ...
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): ...
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
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)
...
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)
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
...
| Approach | Performance | Simplicity | When to Use | | --------------- | ----------- | ---------- | ------------------------------ | | In-memory cache | ⭐⭐⭐ | ⭐⭐ | Hot data, read-heavy | | Database query | ⭐ | ⭐⭐⭐ | Simple CRUD, occasional access | | Redis cache | ⭐⭐ | ⭐ | Distributed caching needed |
Rule: In a distributed system, you can have at most 2 of 3:
Choices:
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): ...
[PROJECT_NAME] architectural principles:
docs/adr/Books:
Websites:
Version: 1.0.0 Type: Knowledge skill (no scripts) See Also: documentation-guide (for ADR formatting), python-standards, code-review
testing
Complete testing methodology - TDD, progression tracking, regression prevention, and test patterns
documentation
GenAI-powered semantic validation - detects outdated docs, version mismatches, and architectural drift
development
Security best practices, API key management, input validation. Use when handling secrets, user input, or security-sensitive code.
research
Research methodology and best practices for finding existing patterns