architecture-patterns
npx machina-cli add skill wpank/ai/architecture-patterns --openclawArchitecture Patterns
WHAT
Backend architecture patterns for building maintainable, testable systems: Clean Architecture, Hexagonal Architecture, and Domain-Driven Design.
WHEN
- Designing new backend systems from scratch
- Refactoring monoliths for better maintainability
- Establishing architecture standards for teams
- Creating testable, mockable codebases
- Planning microservices decomposition
KEYWORDS
clean architecture, hexagonal, ports and adapters, DDD, domain-driven design, layers, entities, use cases, repositories, aggregates, bounded contexts
Installation
OpenClaw / Moltbot / Clawbot
npx clawhub@latest install architecture-patterns
Decision Framework: Which Pattern?
| Situation | Recommended Pattern |
|---|---|
| Simple CRUD app | None (over-engineering) |
| Medium complexity, team standardization | Clean Architecture |
| Multiple external integrations that change frequently | Hexagonal (Ports & Adapters) |
| Complex business domain with many rules | Domain-Driven Design |
| Large system with multiple teams | DDD + Bounded Contexts |
Quick Reference
Clean Architecture Layers
┌──────────────────────────────────────┐
│ Frameworks & Drivers (UI, DB) │ ← Outer: Can change
├──────────────────────────────────────┤
│ Interface Adapters │ ← Controllers, Gateways
├──────────────────────────────────────┤
│ Use Cases │ ← Application Logic
├──────────────────────────────────────┤
│ Entities │ ← Core Business Rules
└──────────────────────────────────────┘
Dependency Rule: Dependencies point INWARD only. Inner layers never import outer layers.
Hexagonal Architecture
┌─────────────┐
┌────│ Adapter │────┐ (REST API)
│ └─────────────┘ │
▼ ▼
┌──────┐ ┌──────────┐
│ Port │◄────────────►│ Domain │
└──────┘ └──────────┘
▲ ▲
│ ┌─────────────┐ │
└────│ Adapter │────┘ (Database)
└─────────────┘
Ports: Interfaces defining what the domain needs Adapters: Implementations (swappable for testing)
Directory Structure
app/
├── domain/ # Entities & business rules (innermost)
│ ├── entities/
│ │ └── user.py
│ ├── value_objects/
│ │ └── email.py
│ └── interfaces/ # Ports
│ └── user_repository.py
├── use_cases/ # Application business rules
│ └── create_user.py
├── adapters/ # Interface implementations
│ ├── repositories/
│ │ └── postgres_user_repository.py
│ └── controllers/
│ └── user_controller.py
└── infrastructure/ # Framework & external concerns
├── database.py
└── config.py
Pattern 1: Clean Architecture
Entity (Domain Layer)
from dataclasses import dataclass
from datetime import datetime
@dataclass
class User:
"""Core entity - NO framework dependencies."""
id: str
email: str
name: str
created_at: datetime
is_active: bool = True
def deactivate(self):
"""Business rule in entity."""
self.is_active = False
def can_place_order(self) -> bool:
return self.is_active
Port (Interface)
from abc import ABC, abstractmethod
from typing import Optional
class IUserRepository(ABC):
"""Port: defines contract, no implementation."""
@abstractmethod
async def find_by_id(self, user_id: str) -> Optional[User]:
pass
@abstractmethod
async def save(self, user: User) -> User:
pass
Use Case (Application Layer)
@dataclass
class CreateUserRequest:
email: str
name: str
@dataclass
class CreateUserResponse:
user: Optional[User]
success: bool
error: Optional[str] = None
class CreateUserUseCase:
"""Use case: orchestrates business logic."""
def __init__(self, user_repository: IUserRepository):
self.user_repository = user_repository # Injected dependency
async def execute(self, request: CreateUserRequest) -> CreateUserResponse:
# Business validation
existing = await self.user_repository.find_by_email(request.email)
if existing:
return CreateUserResponse(user=None, success=False, error="Email exists")
# Create entity
user = User(
id=str(uuid.uuid4()),
email=request.email,
name=request.name,
created_at=datetime.now()
)
saved = await self.user_repository.save(user)
return CreateUserResponse(user=saved, success=True)
Adapter (Implementation)
class PostgresUserRepository(IUserRepository):
"""Adapter: PostgreSQL implementation of the port."""
def __init__(self, pool: asyncpg.Pool):
self.pool = pool
async def find_by_id(self, user_id: str) -> Optional[User]:
async with self.pool.acquire() as conn:
row = await conn.fetchrow(
"SELECT * FROM users WHERE id = $1", user_id
)
return self._to_entity(row) if row else None
async def save(self, user: User) -> User:
async with self.pool.acquire() as conn:
await conn.execute(
"""INSERT INTO users (id, email, name, created_at, is_active)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (id) DO UPDATE SET email=$2, name=$3, is_active=$5""",
user.id, user.email, user.name, user.created_at, user.is_active
)
return user
Pattern 2: Hexagonal (Ports & Adapters)
Best when you have multiple external integrations that may change.
# Domain Service (Core)
class OrderService:
def __init__(
self,
order_repo: OrderRepositoryPort, # Port
payment: PaymentGatewayPort, # Port
notifications: NotificationPort # Port
):
self.orders = order_repo
self.payments = payment
self.notifications = notifications
async def place_order(self, order: Order) -> OrderResult:
# Pure business logic - no infrastructure details
if not order.is_valid():
return OrderResult(success=False, error="Invalid order")
payment = await self.payments.charge(order.total, order.customer_id)
if not payment.success:
return OrderResult(success=False, error="Payment failed")
order.mark_as_paid()
saved = await self.orders.save(order)
await self.notifications.send(order.customer_email, "Order confirmed")
return OrderResult(success=True, order=saved)
# Adapters (swap these for testing or changing providers)
class StripePaymentAdapter(PaymentGatewayPort):
async def charge(self, amount: Money, customer: str) -> PaymentResult:
# Real Stripe implementation
...
class MockPaymentAdapter(PaymentGatewayPort):
async def charge(self, amount: Money, customer: str) -> PaymentResult:
return PaymentResult(success=True, transaction_id="mock-123")
Pattern 3: Domain-Driven Design
For complex business domains with many rules.
Value Objects (Immutable)
@dataclass(frozen=True)
class Email:
"""Value object: validated, immutable."""
value: str
def __post_init__(self):
if "@" not in self.value:
raise ValueError("Invalid email")
@dataclass(frozen=True)
class Money:
amount: int # cents
currency: str
def add(self, other: "Money") -> "Money":
if self.currency != other.currency:
raise ValueError("Currency mismatch")
return Money(self.amount + other.amount, self.currency)
Aggregates (Consistency Boundaries)
class Order:
"""Aggregate root: enforces invariants."""
def __init__(self, id: str, customer: Customer):
self.id = id
self.customer = customer
self.items: List[OrderItem] = []
self.status = OrderStatus.PENDING
self._events: List[DomainEvent] = []
def add_item(self, product: Product, quantity: int):
"""Business logic in aggregate."""
if quantity > product.max_quantity:
raise ValueError(f"Max {product.max_quantity} allowed")
item = OrderItem(product, quantity)
self.items.append(item)
self._events.append(ItemAddedEvent(self.id, item))
def submit(self):
"""State transition with invariant enforcement."""
if not self.items:
raise ValueError("Cannot submit empty order")
if self.status != OrderStatus.PENDING:
raise ValueError("Order already submitted")
self.status = OrderStatus.SUBMITTED
self._events.append(OrderSubmittedEvent(self.id))
Repository Pattern
class OrderRepository:
"""Persist/retrieve aggregates, publish domain events."""
async def save(self, order: Order):
await self._persist(order)
await self._publish_events(order._events)
order._events.clear()
Testing Benefits
All patterns enable the same testing approach:
# Test with mock adapter
async def test_create_user():
mock_repo = MockUserRepository()
use_case = CreateUserUseCase(user_repository=mock_repo)
result = await use_case.execute(CreateUserRequest(
email="test@example.com",
name="Test User"
))
assert result.success
assert result.user.email == "test@example.com"
NEVER
- Anemic Domain Models: Entities with only data, no behavior (put logic IN entities)
- Framework Coupling: Business logic importing Flask, FastAPI, Django ORM
- Fat Controllers: Business logic in HTTP handlers
- Leaky Abstractions: Repository returning ORM objects instead of domain entities
- Skipping Layers: Controller directly accessing database
- Over-Engineering: Using Clean Architecture for simple CRUD apps
- Circular Dependencies: Use cases importing controllers
Source
git clone https://github.com/wpank/ai/blob/main/skills/backend/architecture-patterns/SKILL.mdView on GitHub Overview
This skill exposes backend architecture patterns (Clean Architecture, Hexagonal Architecture, and Domain-Driven Design) to build maintainable, testable systems. It helps teams standardize structure and improve testability when designing new systems or refactoring monoliths.
How This Skill Works
Technically, it guides organizing code into inner domain entities, use cases, and outer interfaces (ports) with adapters implementing those ports. It also outlines a directory structure and maps domain concepts (entities, aggregates, bounded contexts) to architectural layers.
When to Use It
- Designing new backend systems from scratch
- Refactoring monoliths for better maintainability
- Establishing architecture standards for teams
- Creating testable, mockable codebases
- Planning microservices decomposition
Quick Start
- Step 1: Identify the core domain and define domain entities, value objects, and use cases.
- Step 2: Select a pattern (Clean Architecture, Hexagonal, or DDD) based on project needs.
- Step 3: Organize code into layers/ports/adapters and implement tests for ports from the start.
Best Practices
- Assess complexity and integration volatility to choose between Clean Architecture, Hexagonal, or DDD.
- Define ports and adapters early to enable testing and swapping implementations.
- Enforce inward-only dependencies per the Core Layer rule in Clean Architecture.
- Model the domain with clear entities, aggregates, and bounded contexts.
- Codify architecture decisions in team standards and starter templates.
Example Use Cases
- New backend service for order processing using Clean Architecture.
- Monolith refactor as Hexagonal to swap database adapters without touching domain logic.
- DDD-driven microservice decomposition with bounded contexts for catalog, orders, and payments.
- Testable integration tests using ports and mock adapters to isolate external systems.
- Cross-team architecture standards documented and enforced via shared templates.