architecture-patternsProvides backend architecture patterns (Clean Architecture, Hexagonal, DDD) for building maintainable, testable, and scalable systems with clear layering and...
Install via ClawdBot CLI:
clawdbot install wpank/architecture-patternsBackend architecture patterns for building maintainable, testable systems: Clean Architecture, Hexagonal Architecture, and Domain-Driven Design.
clean architecture, hexagonal, ports and adapters, DDD, domain-driven design, layers, entities, use cases, repositories, aggregates, bounded contexts
| 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 |
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā 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.
āāāāāāāāāāāāāāā
āāāāāā Adapter āāāāāā (REST API)
ā āāāāāāāāāāāāāāā ā
ā¼ ā¼
āāāāāāāā āāāāāāāāāāāā
ā Port āāāāāāāāāāāāāāāŗā Domain ā
āāāāāāāā āāāāāāāāāāāā
ā² ā²
ā āāāāāāāāāāāāāāā ā
āāāāāā Adapter āāāāāā (Database)
āāāāāāāāāāāāāāā
Ports: Interfaces defining what the domain needs
Adapters: Implementations (swappable for testing)
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
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
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
@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)
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
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")
For complex business domains with many rules.
@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)
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))
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()
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"
Generated Mar 1, 2026
Building a new online marketplace with complex business rules like inventory management, pricing strategies, and user roles. Clean Architecture ensures separation of domain logic from web frameworks and databases, facilitating testing and future scaling.
Refactoring a legacy monolith for a hospital to integrate with multiple external systems (e.g., EHRs, billing APIs). Hexagonal Architecture allows swapping adapters for different integrations, maintaining core domain logic while adapting to changing external requirements.
Developing a financial application with stringent compliance rules and frequent regulatory updates. Domain-Driven Design helps model complex business domains like transactions and risk assessments, using bounded contexts to manage different regulatory modules.
Establishing architecture standards for a growing SaaS company with multiple teams working on microservices. Clean Architecture provides a consistent structure across services, improving maintainability and enabling parallel development with clear dependencies.
Creating a backend for an IoT platform that handles data from various device types and external APIs. Hexagonal Architecture facilitates testing by mocking adapters for different device protocols, ensuring core domain logic remains isolated and adaptable.
Offering architecture consulting to help companies refactor legacy systems or design new applications. Revenue comes from project-based fees or retainer models, leveraging expertise in patterns like Clean Architecture and DDD to improve client codebases.
Developing and licensing a SaaS platform built with these architecture patterns, such as a CRM or project management tool. Revenue is generated through subscription tiers, with patterns ensuring scalability and easy integration for enterprise clients.
Providing training sessions and workshops on architecture patterns for development teams. Revenue comes from course fees and corporate training packages, helping organizations adopt best practices for maintainable and testable systems.
š¬ Integration Tip
Use dependency injection to inject adapters like repositories or external APIs, ensuring core domain logic remains framework-agnostic and easily testable with mocks.
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
Provides a 7-step debugging protocol plus language-specific commands to systematically identify, verify, and fix software bugs across multiple environments.
A comprehensive skill for using the Cursor CLI agent for various software engineering tasks (updated for 2026 features, includes tmux automation guide).
Write, run, and manage unit, integration, and E2E tests across TypeScript, Python, and Swift using recommended frameworks.
Control and operate Opencode via slash commands. Use this skill to manage sessions, select models, switch agents (plan/build), and coordinate coding through Opencode.
Coding style memory that adapts to your preferences, conventions, and patterns for consistent coding.