Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Software architecture patterns skill from a 146-skill AI agent marketplace for Claude Code (Opus 4.6/Sonnet 4.6).
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/details.md
1# architecture-patterns — detailed patterns and worked examples23## Clean Architecture — Directory Structure45```6app/7├── domain/ # Entities, value objects, interfaces8│ ├── entities/9│ │ ├── user.py10│ │ └── order.py11│ ├── value_objects/12│ │ ├── email.py13│ │ └── money.py14│ └── interfaces/ # Abstract ports (no implementations)15│ ├── user_repository.py16│ └── payment_gateway.py17├── use_cases/ # Application business rules18│ ├── create_user.py19│ ├── process_order.py20│ └── send_notification.py21├── adapters/ # Concrete implementations22│ ├── repositories/23│ │ ├── postgres_user_repository.py24│ │ └── redis_cache_repository.py25│ ├── controllers/26│ │ └── user_controller.py27│ └── gateways/28│ ├── stripe_payment_gateway.py29│ └── sendgrid_email_gateway.py30└── infrastructure/ # Framework wiring, config, DI container31├── database.py32├── config.py33└── logging.py34```3536**Dependency rule in one sentence:** every `import` statement in `domain/` and `use_cases/` must point only toward `domain/`; nothing in those layers may import from `adapters/` or `infrastructure/`.3738## Clean Architecture — Core Implementation3940```python41# domain/entities/user.py42from dataclasses import dataclass43from datetime import datetime4445@dataclass46class User:47"""Core user entity — no framework dependencies."""48id: str49email: str50name: str51created_at: datetime52is_active: bool = True5354def deactivate(self):55self.is_active = False5657def can_place_order(self) -> bool:58return self.is_active596061# domain/interfaces/user_repository.py62from abc import ABC, abstractmethod63from typing import Optional64from domain.entities.user import User6566class IUserRepository(ABC):67"""Port: defines contract, no implementation details."""6869@abstractmethod70async def find_by_id(self, user_id: str) -> Optional[User]: ...7172@abstractmethod73async def find_by_email(self, email: str) -> Optional[User]: ...7475@abstractmethod76async def save(self, user: User) -> User: ...7778@abstractmethod79async def delete(self, user_id: str) -> bool: ...808182# use_cases/create_user.py83from dataclasses import dataclass84from datetime import datetime85from typing import Optional86import uuid87from domain.entities.user import User88from domain.interfaces.user_repository import IUserRepository8990@dataclass91class CreateUserRequest:92email: str93name: str9495@dataclass96class CreateUserResponse:97user: Optional[User]98success: bool99error: Optional[str] = None100101class CreateUserUseCase:102"""Use case: orchestrates business logic, no HTTP or DB details."""103104def __init__(self, user_repository: IUserRepository):105self.user_repository = user_repository106107async def execute(self, request: CreateUserRequest) -> CreateUserResponse:108existing = await self.user_repository.find_by_email(request.email)109if existing:110return CreateUserResponse(user=None, success=False, error="Email already exists")111112user = User(113id=str(uuid.uuid4()),114email=request.email,115name=request.name,116created_at=datetime.now(),117)118saved_user = await self.user_repository.save(user)119return CreateUserResponse(user=saved_user, success=True)120121122# adapters/repositories/postgres_user_repository.py123from domain.interfaces.user_repository import IUserRepository124from domain.entities.user import User125from typing import Optional126import asyncpg127128class PostgresUserRepository(IUserRepository):129"""Adapter: PostgreSQL implementation of the user port."""130131def __init__(self, pool: asyncpg.Pool):132self.pool = pool133134async def find_by_id(self, user_id: str) -> Optional[User]:135async with self.pool.acquire() as conn:136row = await conn.fetchrow("SELECT * FROM users WHERE id = $1", user_id)137return self._to_entity(row) if row else None138139async def find_by_email(self, email: str) -> Optional[User]:140async with self.pool.acquire() as conn:141row = await conn.fetchrow("SELECT * FROM users WHERE email = $1", email)142return self._to_entity(row) if row else None143144async def save(self, user: User) -> User:145async with self.pool.acquire() as conn:146await conn.execute(147"""148INSERT INTO users (id, email, name, created_at, is_active)149VALUES ($1, $2, $3, $4, $5)150ON CONFLICT (id) DO UPDATE151SET email = $2, name = $3, is_active = $5152""",153user.id, user.email, user.name, user.created_at, user.is_active,154)155return user156157async def delete(self, user_id: str) -> bool:158async with self.pool.acquire() as conn:159result = await conn.execute("DELETE FROM users WHERE id = $1", user_id)160return result == "DELETE 1"161162def _to_entity(self, row) -> User:163return User(164id=row["id"], email=row["email"], name=row["name"],165created_at=row["created_at"], is_active=row["is_active"],166)167168169# adapters/controllers/user_controller.py170from fastapi import APIRouter, Depends, HTTPException171from pydantic import BaseModel172from use_cases.create_user import CreateUserUseCase, CreateUserRequest173174router = APIRouter()175176class CreateUserDTO(BaseModel):177email: str178name: str179180@router.post("/users")181async def create_user(182dto: CreateUserDTO,183use_case: CreateUserUseCase = Depends(get_create_user_use_case),184):185"""Controller handles HTTP only — no business logic lives here."""186response = await use_case.execute(CreateUserRequest(email=dto.email, name=dto.name))187if not response.success:188raise HTTPException(status_code=400, detail=response.error)189return {"user": response.user}190```191192## Hexagonal Architecture — Ports and Adapters193194```python195# Core domain service — no infrastructure dependencies196class OrderService:197def __init__(198self,199order_repository: OrderRepositoryPort,200payment_gateway: PaymentGatewayPort,201notification_service: NotificationPort,202):203self.orders = order_repository204self.payments = payment_gateway205self.notifications = notification_service206207async def place_order(self, order: Order) -> OrderResult:208if not order.is_valid():209return OrderResult(success=False, error="Invalid order")210211payment = await self.payments.charge(amount=order.total, customer=order.customer_id)212if not payment.success:213return OrderResult(success=False, error="Payment failed")214215order.mark_as_paid()216saved_order = await self.orders.save(order)217await self.notifications.send(218to=order.customer_email,219subject="Order confirmed",220body=f"Order {order.id} confirmed",221)222return OrderResult(success=True, order=saved_order)223224225# Ports (driving and driven interfaces)226class OrderRepositoryPort(ABC):227@abstractmethod228async def save(self, order: Order) -> Order: ...229230class PaymentGatewayPort(ABC):231@abstractmethod232async def charge(self, amount: Money, customer: str) -> PaymentResult: ...233234class NotificationPort(ABC):235@abstractmethod236async def send(self, to: str, subject: str, body: str): ...237238239# Production adapter: Stripe240class StripePaymentAdapter(PaymentGatewayPort):241def __init__(self, api_key: str):242import stripe243stripe.api_key = api_key244self._stripe = stripe245246async def charge(self, amount: Money, customer: str) -> PaymentResult:247try:248charge = self._stripe.Charge.create(249amount=amount.cents, currency=amount.currency, customer=customer250)251return PaymentResult(success=True, transaction_id=charge.id)252except self._stripe.error.CardError as e:253return PaymentResult(success=False, error=str(e))254255256# Test adapter: no external dependencies257class MockPaymentAdapter(PaymentGatewayPort):258async def charge(self, amount: Money, customer: str) -> PaymentResult:259return PaymentResult(success=True, transaction_id="mock-txn-123")260```261262## DDD — Value Objects and Aggregates263264```python265# Value Objects: immutable, validated at construction266from dataclasses import dataclass267268@dataclass(frozen=True)269class Email:270value: str271272def __post_init__(self):273if "@" not in self.value or "." not in self.value.split("@")[-1]:274raise ValueError(f"Invalid email: {self.value}")275276@dataclass(frozen=True)277class Money:278amount: int # cents279currency: str280281def __post_init__(self):282if self.amount < 0:283raise ValueError("Money amount cannot be negative")284if self.currency not in {"USD", "EUR", "GBP"}:285raise ValueError(f"Unsupported currency: {self.currency}")286287def add(self, other: "Money") -> "Money":288if self.currency != other.currency:289raise ValueError("Currency mismatch")290return Money(self.amount + other.amount, self.currency)291292293# Aggregate root: enforces all invariants for its cluster of entities294class Order:295def __init__(self, id: str, customer_id: str):296self.id = id297self.customer_id = customer_id298self.items: list[OrderItem] = []299self.status = OrderStatus.PENDING300self._events: list[DomainEvent] = []301302def add_item(self, product: Product, quantity: int):303if self.status != OrderStatus.PENDING:304raise ValueError("Cannot modify a submitted order")305item = OrderItem(product=product, quantity=quantity)306self.items.append(item)307self._events.append(ItemAddedEvent(order_id=self.id, item=item))308309@property310def total(self) -> Money:311totals = [item.subtotal() for item in self.items]312return sum(totals[1:], totals[0]) if totals else Money(0, "USD")313314def submit(self):315if not self.items:316raise ValueError("Cannot submit an empty order")317if self.status != OrderStatus.PENDING:318raise ValueError("Order already submitted")319self.status = OrderStatus.SUBMITTED320self._events.append(OrderSubmittedEvent(order_id=self.id))321322def pop_events(self) -> list[DomainEvent]:323events, self._events = self._events, []324return events325326327# Repository: persist and reconstitute aggregates328class OrderRepository(ABC):329@abstractmethod330async def find_by_id(self, order_id: str) -> Optional[Order]: ...331332@abstractmethod333async def save(self, order: Order) -> None: ...334# Implementations persist events via pop_events() after writing state335```336