architecture-patterns

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.

$ Installer

git clone https://github.com/akaszubski/anyclaude-local /tmp/anyclaude-local && cp -r /tmp/anyclaude-local/plugins/autonomous-dev/skills/architecture-patterns ~/.claude/skills/anyclaude-local

// tip: Run this command in your terminal to install the skill


name: architecture-patterns type: knowledge description: 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. keywords: architecture, design, pattern, decision, tradeoffs, adr, system design, scalability, microservices, mvc, design patterns, solid auto_activate: true

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

  1. Proposed: Draft for review
  2. Accepted: Team approved, implementing
  3. Deprecated: No longer recommended, but not replaced
  4. 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

ApproachPerformanceSimplicityWhen 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