Skill: wpank/architecture-patterns (version 1.0.0)

--- name: architecture-patterns model: reasoning --- # Architecture 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 --- ## 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) ```python 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) ```python 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) ```python @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) ```python 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. ```python # 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) ```python @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) ```python 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 ```python 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: ```python # 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="[REDACTED_SECRET]", name="Test User" )) assert result.success assert result.user.email == "[REDACTED_SECRET]" ``` --- ## 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

path: README.md
content: # Architecture Patterns

Backend architecture patterns for building maintainable, testable systems: Clean Architecture, Hexagonal Architecture, and Domain-Driven Design.

## What's Inside

- Decision Framework — which pattern for which situation
- Clean Architecture — layers (entities, use cases, interface adapters, frameworks), dependency rule, implementation examples
- Hexagonal Architecture (Ports & Adapters) — ports as interfaces, adapters as swappable implementations
- Domain-Driven Design — value objects, aggregates, domain events, repository pattern
- Directory Structure for layered architecture
- Entity, Port, Use Case, and Adapter implementation patterns (Python)
- Testing Benefits — mock adapters for easy unit testing

## When to Use

- Designing new backend systems from scratch
- Refactoring monoliths for better maintainability
- Establishing architecture standards for teams
- Creating testable, mockable codebases
- Planning microservices decomposition

## Installation

```bash
npx add https://github.com/wpank/ai/tree/main/skills/backend/architecture-patterns
```

### Manual Installation

#### Cursor (per-project)

From your project root:

```bash
mkdir -p .cursor/skills
cp -r ~/.ai-skills/skills/backend/architecture-patterns .cursor/skills/architecture-patterns
```

#### Cursor (global)

```bash
mkdir -p ~/.cursor/skills
cp -r ~/.ai-skills/skills/backend/architecture-patterns ~/.cursor/skills/architecture-patterns
```

#### Claude Code (per-project)

From your project root:

```bash
mkdir -p .claude/skills
cp -r ~/.ai-skills/skills/backend/architecture-patterns .claude/skills/architecture-patterns
```

#### Claude Code (global)

```bash
mkdir -p ~/.claude/skills
cp -r ~/.ai-skills/skills/backend/architecture-patterns ~/.claude/skills/architecture-patterns
```

## Related Skills

- `architecture-decision-records` — Document architecture decisions made using these patterns
- `microservices-patterns` — Distributed system patterns that build on these foundations
- `service-layer-architecture` — Controller-service-query layered architecture

---

Part of the [Backend](..) skill category.


path: data/patterns.csv
content: id,name,category,description,use_case,tradeoffs,complexity,scale
1,Monolith,Application Architecture,Single deployable unit containing all application logic and components,Small teams building MVPs or simple applications with limited scope,Simple deployment and debugging but poor scalability and tight coupling,low,small
2,Modular Monolith,Application Architecture,Monolith organized into well-defined modules with clear boundaries and interfaces,Teams wanting modularity without distributed system complexity; migration stepping stone,Better organization than monolith but still single deployment unit; good balance of simplicity and structure,medium,medium
3,Microservices,Application Architecture,Application decomposed into small independent services each owning its domain and data,Large teams needing independent deployment scaling and technology diversity,Independent scaling and deployment but operational complexity and distributed system challenges,high,enterprise
4,Serverless,Application Architecture,Application built on cloud functions that execute on demand without managing infrastructure,Event-driven workloads with variable traffic; rapid prototyping; cost-sensitive projects,Zero infrastructure management and pay-per-use but vendor lock-in cold starts and debugging difficulty,medium,large
5,Service-Oriented Architecture (SOA),Application Architecture,Coarse-grained services communicating via enterprise service bus with shared data contracts,Enterprise integration across legacy systems; large organizations with diverse technology stacks,Reusability and enterprise integration but heavyweight middleware and complex governance,high,enterprise
6,Event-Driven Architecture,Application Architecture,Components communicate through asynchronous events enabling loose coupling and reactivity,Real-time processing systems; systems requiring high decoupling; IoT and streaming platforms,Excellent decoupling and scalability but eventual consistency complexity and harder debugging,high,large
7,CQRS,Application Architecture,Separates read and write models allowing independent optimization of query and command paths,Systems with asymmetric read/write loads; complex domains requiring different read/write representations,Optimized read/write performance but increased complexity and eventual consistency between models,high,large
8,Event Sourcing,Application Architecture,Stores all state changes as immutable events rebuilding current state by replaying the event log,Audit-critical systems; systems needing complete history; financial and compliance applications,Complete audit trail and temporal queries but storage growth complexity and eventual consistency,high,large
9,Clean Architecture,Structural Patterns,Concentric layers with dependencies pointing inward; domain at center independent of frameworks,Applications requiring long-term maintainability testability and framework independence,High testability and flexibility but more boilerplate and initial setup overhead,medium,medium
10,Hexagonal (Ports & Adapters),Structural Patterns,Core domain surrounded by ports defining interfaces and adapters implementing external integrations,Applications needing swappable infrastructure; systems with multiple integration points,Easy to test and swap integrations but requires discipline to maintain port/adapter boundaries,medium,medium
11,Onion Architecture,Structural Patterns,Similar to clean architecture with domain model at core and infrastructure at outermost layer,Domain-heavy applications where business logic must remain isolated from infrastructure concerns,Strong domain isolation and testability but learning curve and potential over-engineering for simple apps,medium,medium
12,Layered Architecture,Structural Patterns,Horizontal layers where each layer only communicates with the layer directly below it,Traditional enterprise applications; teams familiar with n-tier patterns; CRUD-heavy applications,Simple to understand and implement but can lead to tight coupling between layers and god classes,low,small
13,Domain-Driven Design,Structural Patterns,Software design approach aligning code structure with business domains using ubiquitous language,Complex business domains with rich logic; large teams needing shared understanding of the domain,Deep business alignment and maintainability but steep learning curve and overhead for simple domains,high,enterprise
14,Plugin Architecture,Structural Patterns,Core system with extension points allowing third-party plugins to add functionality dynamically,Extensible platforms like IDEs editors and CMS; products requiring customization without core changes,High extensibility and customization but plugin compatibility management and security concerns,medium,large
15,Pipe and Filter,Structural Patterns,Data flows through a series of processing components each transforming input to output,Data processing pipelines; ETL systems; compiler design; stream processing applications,Easy to add remove and reorder processing steps but overhead from data transformation between stages,low,medium
16,Blackboard Pattern,Structural Patterns,Shared knowledge base where specialized subsystems collaboratively build a solution,AI and expert systems; complex problem solving requiring multiple specialized algorithms,Enables collaborative problem solving but complex coordination and potential performance bottlenecks,high,large
17,Request-Response,Communication Patterns,Synchronous communication where client sends request and waits for server response,Simple API interactions; CRUD operations; when immediate response is required,Simple and intuitive but tight coupling between client and server; blocking calls reduce throughput,low,small
18,Publish-Subscribe,Communication Patterns,Publishers emit events to topics and subscribers receive events they are interested in,Broadcasting events to multiple consumers; decoupled notification systems; real-time updates,Excellent decoupling and scalability but message ordering challenges and debugging complexity,medium,large
19,Message Queue,Communication Patterns,Asynchronous communication via queues ensuring reliable message delivery between services,Workload buffering; reliable async processing; handling traffic spikes; background job processing,Reliable delivery and load leveling but added infrastructure complexity and potential message duplication,medium,large
20,API Gateway,Communication Patterns,Single entry point that routes requests to appropriate backend services handling cross-cutting concerns,Microservices architectures needing unified API; mobile backends; rate limiting and auth aggregation,Simplified client interface and centralized concerns but single point of failure and added latency,medium,large
21,Service Mesh,Communication Patterns,Infrastructure layer handling service-to-service communication with observability security and reliability,Large microservices deployments needing consistent networking policies; zero-trust security models,Transparent networking and observability but significant operational complexity and resource overhead,high,enterprise
22,Saga Pattern,Communication Patterns,Manages distributed transactions across services using a sequence of local transactions with compensations,Distributed transactions spanning multiple services; long-running business processes requiring consistency,Maintains consistency without distributed locks but complex compensation logic and partial failure handling,high,large
23,Choreography,Communication Patterns,Services coordinate through events without central controller each reacting to events from others,Simple workflows with few services; when decentralization and autonomy are priorities,No single point of failure and loose coupling but harder to track workflows and potential cyclic dependencies,medium,large
24,Orchestration,Communication Patterns,Central orchestrator coordinates workflow by directing service calls and managing the overall process,Complex workflows requiring visibility; processes needing central error handling and monitoring,Clear workflow visibility and easier debugging but central point of failure and potential bottleneck,medium,large
25,Database per Service,Data Patterns,Each microservice owns and manages its private database with no direct database sharing,Microservices requiring data independence; teams needing autonomous schema evolution,Full data autonomy and independent scaling but cross-service queries are complex and data consistency challenges,high,large
26,Shared Database,Data Patterns,Multiple services access the same database schema sharing tables and data directly,Small teams or monoliths transitioning to services; when strong consistency is critical across services,Simple data access and strong consistency but tight coupling schema change coordination and scaling limits,low,small
27,CQRS with Event Sourcing,Data Patterns,Combines separated read/write models with event log as source of truth for all state changes,High-performance systems needing audit trails; complex domains with asymmetric read/write patterns,Ultimate flexibility and audit capability but highest complexity requiring deep expertise to implement well,high,enterprise
28,Data Lake,Data Patterns,Centralized repository storing raw data at any scale in native format for analytics and processing,Big data analytics; machine learning pipelines; organizations consolidating diverse data sources,Stores any data format at scale but can become a data swamp without governance; query performance varies,medium,enterprise
29,Data Mesh,Data Patterns,Decentralized data architecture treating data as a product owned by domain teams with self-serve platform,Large organizations with many data domains; when centralized data teams become bottlenecks,Domain ownership and scalability but requires organizational change and mature data platform,high,enterprise
30,Polyglot Persistence,Data Patterns,Using different database technologies for different services based on their specific data access patterns,Systems with diverse data needs; when no single database type fits all service requirements,Optimal storage per use case but operational complexity of managing multiple database technologies,medium,large
31,Read Replicas,Data Patterns,Primary database handles writes while read-only copies serve read queries distributing read load,Read-heavy applications; reporting systems; geographic distribution of read traffic,Improved read performance and availability but replication lag and eventual consistency for reads,medium,large
32,Sharding,Data Patterns,Horizontally partitioning data across multiple database instances based on a shard key,Very large datasets exceeding single database capacity; high-throughput write-heavy applications,Near-linear horizontal scaling but cross-shard queries are complex and rebalancing is operationally heavy,high,enterprise
33,Blue-Green Deployment,Deployment Patterns,Two identical production environments where traffic switches from current (blue) to new (green) version,Zero-downtime deployments; environments requiring instant rollback capability,Instant rollback and zero downtime but double infrastructure cost and database migration complexity,medium,large
34,Canary Release,Deployment Patterns,Gradually rolling out changes to a small subset of users before full deployment to catch issues early,Risk-sensitive deployments; validating changes with real traffic before wide release,Early issue detection with limited blast radius but requires traffic splitting infrastructure and monitoring,medium,large
35,Rolling Update,Deployment Patterns,Gradually replacing instances of the old version with new version one at a time across the fleet,Standard deployments for stateless services; Kubernetes default deployment strategy,No extra infrastructure needed and gradual rollout but mixed versions during update and slower rollback,low,medium
36,Feature Flags,Deployment Patterns,Toggles controlling feature visibility at runtime allowing deployment independent of feature release,Trunk-based development; A/B testing; gradual feature rollouts; kill switches for new features,Decouple deployment from release and enable testing in production but flag debt and increased code complexity,medium,large
37,Strangler Fig,Deployment Patterns,Incrementally replacing legacy system by routing requests to new implementation piece by piece,Legacy system modernization; gradual migration from monolith to microservices,Low-risk incremental migration but requires routing layer and extended period of running two systems,medium,large
38,Branch by Abstraction,Deployment Patterns,Introducing abstraction layer to allow old and new implementations to coexist during migration,Replacing components within a monolith; swapping infrastructure dependencies without big-bang migration,Safe incremental replacement but abstraction layer overhead and temporary code complexity,medium,medium
39,Shadow Deploy,Deployment Patterns,New version receives copy of production traffic for comparison but responses are discarded,Validating performance and correctness of new version with real traffic without user impact,Real traffic validation without risk but double resource usage and no validation of write-path side effects,high,enterprise
40,A/B Testing,Deployment Patterns,Running two variants simultaneously with different user segments to measure which performs better,Product feature validation; UI/UX optimization; conversion rate optimization; data-driven decisions,Data-driven decisions and measurable impact but requires statistical rigor and user segmentation infrastructure,medium,large
41,Circuit Breaker,Resilience Patterns,Prevents cascading failures by stopping calls to failing services and providing fallback behavior,Protecting services from downstream failures; preventing cascade failures in distributed systems,Prevents cascade failures and enables graceful degradation but adds complexity and requires tuning thresholds,medium,large
42,Bulkhead,Resilience Patterns,Isolates components into pools so failure in one does not drain resources from others,Isolating critical from non-critical workloads; preventing resource exhaustion from affecting all consumers,Failure isolation and resource protection but reduced overall resource utilization and configuration complexity,medium,large
43,Retry with Backoff,Resilience Patterns,Automatically retries failed operations with increasing delays between attempts to handle transient failures,Handling transient network failures; unreliable external service calls; eventual consistency scenarios,Handles transient failures transparently but can amplify load on struggling services without proper limits,low,medium
44,Timeout,Resilience Patterns,Sets maximum duration for operations preventing indefinite waits for unresponsive services,All remote service calls; database queries; any operation that could hang indefinitely,Prevents resource exhaustion from hung calls but too-aggressive timeouts cause false failures on slow operations,low,medium
45,Fallback,Resilience Patterns,Provides alternative behavior or cached data when primary operation fails ensuring degraded but functional service,User-facing services needing graceful degradation; systems with acceptable fallback responses,Maintains availability during failures but fallback data may be stale and adds maintenance burden,low,medium
46,Cache-Aside,Resilience Patterns,Application checks cache before database loading cache on miss; improves performance and provides resilience,Read-heavy workloads; reducing database load; providing stale data during database outages,Improved performance and database resilience but cache invalidation complexity and potential stale data,medium,large
47,Health Check,Resilience Patterns,Services expose endpoints reporting their status enabling load balancers and orchestrators to manage traffic,Container orchestration; load balancer routing; service discovery; monitoring and alerting,Automated failure detection and traffic management but health check logic must accurately reflect service state,low,medium
48,Rate Limiting,Resilience Patterns,Controls the rate of incoming requests preventing abuse and ensuring fair resource usage across consumers,Public APIs; multi-tenant systems; protecting services from traffic spikes and denial of service,Protects service stability and ensures fairness but can reject legitimate traffic if limits are too aggressive,medium,large


path: templates/clean-architecture/README.md
content: # =============================================================================
# Clean Architecture - Project Scaffold
# =============================================================================
#
# Directory structure implementing Uncle Bob's Clean Architecture
# with TypeScript. Dependencies always point inward.
#
# Usage:
#   Copy this directory as your project starting point.
#   Replace the example User entity and CreateUser use case
#   with your own domain concepts.
#
# Dependency Rule:
#   domain ← use-cases ← adapters ← infrastructure
#   Inner layers NEVER import from outer layers.
#   Inner layers define interfaces. Outer layers implement them.
#
# =============================================================================

## Directory Structure

```
src/
├── domain/                     # Layer 1: Enterprise Business Rules
│   ├── entities/               # Business objects with behavior
│   │   └── user.entity.ts      # Example: User entity
│   ├── value-objects/           # Immutable, identity-less types
│   │   └── email.value-object.ts
│   ├── interfaces/             # Ports (repository contracts, service contracts)
│   │   └── repository.interface.ts
│   ├── errors/                 # Domain-specific error classes
│   │   └── domain.errors.ts
│   └── events/                 # Domain events
│       └── user-created.event.ts
│
├── use-cases/                  # Layer 2: Application Business Rules
│   ├── create-user.use-case.ts # Example: Create user use case
│   ├── get-user.use-case.ts
│   └── dtos/                   # Request/Response data transfer objects
│       └── user.dto.ts
│
├── adapters/                   # Layer 3: Interface Adapters
│   ├── controllers/            # HTTP/GraphQL request handlers
│   │   └── user.controller.ts  # Example: Express controller
│   ├── repositories/           # Persistence implementations
│   │   └── prisma-user.repository.ts
│   ├── presenters/             # Response formatters
│   │   └── user.presenter.ts
│   └── mappers/                # Entity ↔ persistence model mapping
│       └── user.mapper.ts
│
├── infrastructure/             # Layer 4: Frameworks & Drivers
│   ├── config/                 # Environment, app configuration
│   ├── database/               # Database connection, migrations
│   ├── http/                   # Express/Fastify app setup, middleware
│   └── composition-root.ts     # Wires all dependencies together
│
└── main.ts                     # Entry point — starts the application
```

## Customize

1. **Replace example entities** — Change `user.entity.ts` to your domain
2. **Add value objects** — Create types for Email, Money, UserId, etc.
3. **Define repository interfaces** — One per aggregate root in `domain/interfaces/`
4. **Create use cases** — One class per application operation in `use-cases/`
5. **Implement adapters** — Wire controllers and repositories in `adapters/`
6. **Set up composition root** — Connect everything in `infrastructure/composition-root.ts`

## Key Rules

- `domain/` imports from: nothing (zero external dependencies)
- `use-cases/` imports from: `domain/` only
- `adapters/` imports from: `use-cases/` and `domain/`
- `infrastructure/` imports from: everything (outermost layer)
- Never import framework packages (express, prisma, etc.) in `domain/` or `use-cases/`


path: templates/clean-architecture/src/domain/interfaces/repository.interface.ts
content: // =============================================================================
// Repository Interface — Clean Architecture Domain Layer
// =============================================================================
//
// This interface (port) defines what the domain needs from persistence.
// It lives in the domain layer but is IMPLEMENTED in the adapter layer.
//
// The domain says WHAT it needs. The adapter decides HOW to provide it.
//
// Key rules:
// - Uses domain types (User, not PrismaUser)
// - No mention of SQL, Prisma, MongoDB, or any specific technology
// - One repository per aggregate root
//
// Customize: Replace User with your aggregate root entity.
// =============================================================================

import { User } from "../entities/user.entity";

/**
 * Repository interface for the User aggregate.
 *
 * Implemented by:
 * - PrismaUserRepository (production — in adapters/repositories/)
 * - InMemoryUserRepository (testing)
 */
export interface UserRepository {
  /** Find a user by their unique ID. Returns null if not found. */
  findById(id: string): Promise<User | null>;

  /** Find a user by email address. Returns null if not found. */
  findByEmail(email: string): Promise<User | null>;

  /** Persist a user (create or update). */
  save(user: User): Promise<void>;

  /** Remove a user by ID. */
  delete(id: string): Promise<void>;
}


path: templates/clean-architecture/src/domain/entities/user.entity.ts
content: // =============================================================================
// User Entity — Clean Architecture Domain Layer
// =============================================================================
//
// This entity belongs to the innermost layer. It has ZERO external
// dependencies — no framework imports, no database imports, no HTTP imports.
//
// The entity encapsulates business rules and invariants. External code
// interacts with the entity through its public methods.
//
// Customize: Replace this with your own domain entity.
// =============================================================================

// Value object imports are OK — they're in the same layer
// import { Email } from '../value-objects/email.value-object';
// import { UserId } from '../value-objects/user-id.value-object';

export interface UserProps {
  id: string;
  email: string;
  name: string;
  status: "active" | "suspended" | "deactivated";
  failedLoginAttempts: number;
  createdAt: Date;
}

export class User {
  private constructor(
    public readonly id: string,
    private _email: string,
    private _name: string,
    private _status: "active" | "suspended" | "deactivated",
    private _failedLoginAttempts: number,
    public readonly createdAt: Date
  ) {}

  // ---------- Factory methods ----------

  /** Create a new user. Use this for new registrations. */
  static create(props: { email: string; name: string }): User {
    return new User(
      crypto.randomUUID(),
      props.email,
      props.name,
      "active",
      0,
      new Date()
    );
  }

  /** Reconstitute from persistence. Use this in repository mappers. */
  static reconstitute(props: UserProps): User {
    return new User(
      props.id,
      props.email,
      props.name,
      props.status,
      props.failedLoginAttempts,
      props.createdAt
    );
  }

  // ---------- Getters ----------

  get email(): string {
    return this._email;
  }
  get name(): string {
    return this._name;
  }
  get status(): string {
    return this._status;
  }

  // ---------- Business behavior ----------

  /** Check if the user is allowed to log in. */
  canLogin(): boolean {
    return this._status === "active" && this._failedLoginAttempts < 5;
  }

  /** Record a failed login attempt. Suspends after 5 failures. */
  recordFailedLogin(): void {
    this._failedLoginAttempts++;
    if (this._failedLoginAttempts >= 5) {
      this._status = "suspended";
    }
  }

  /** Record a successful login. Resets failure counter. */
  recordSuccessfulLogin(): void {
    this._failedLoginAttempts = 0;
  }

  /** Deactivate the user account. */
  deactivate(): void {
    if (this._status === "deactivated") {
      throw new Error("User is already deactivated");
    }
    this._status = "deactivated";
  }

  /** Identity-based equality. Two users with the same ID are the same user. */
  equals(other: User): boolean {
    return this.id === other.id;
  }
}


path: templates/clean-architecture/src/use-cases/create-user.use-case.ts
content: // =============================================================================
// Create User Use Case — Clean Architecture Application Layer
// =============================================================================
//
// Use cases contain application-specific business logic. They orchestrate
// entities and repository interfaces to accomplish a specific task.
//
// This layer imports from domain/ only — never from adapters/ or infrastructure/.
//
// Key rules:
// - One class per use case with a single execute() method
// - Takes a plain DTO as input, returns a plain DTO as output
// - No framework types (no Express Request, no Prisma types)
// - Dependencies injected through constructor (interfaces, not implementations)
//
// Customize: Replace with your own use case. Follow the same structure.
// =============================================================================

import { User } from "../domain/entities/user.entity";
import { UserRepository } from "../domain/interfaces/repository.interface";

// ---------- Request / Response DTOs ----------
// Plain objects — no framework dependencies, no entity references

export interface CreateUserRequest {
  email: string;
  name: string;
}

export interface CreateUserResponse {
  id: string;
  email: string;
  name: string;
}

// ---------- Use Case ----------

export class CreateUserUseCase {
  constructor(
    // Depends on the interface, not the implementation.
    // PrismaUserRepository or InMemoryUserRepository — doesn't matter here.
    private readonly userRepository: UserRepository
  ) {}

  async execute(request: CreateUserRequest): Promise<CreateUserResponse> {
    // 1. Validate business rules
    const existingUser = await this.userRepository.findByEmail(request.email);
    if (existingUser) {
      throw new UserAlreadyExistsError(request.email);
    }

    // 2. Create domain entity (entity handles its own validation)
    const user = User.create({
      email: request.email,
      name: request.name,
    });

    // 3. Persist through repository interface
    await this.userRepository.save(user);

    // 4. Return response DTO (not the entity itself)
    return {
      id: user.id,
      email: user.email,
      name: user.name,
    };
  }
}

// ---------- Domain Error ----------
// Errors are domain concepts — they describe what went wrong in business terms.

export class UserAlreadyExistsError extends Error {
  constructor(email: string) {
    super(`A user with email "${email}" already exists`);
    this.name = "UserAlreadyExistsError";
  }
}


path: templates/clean-architecture/src/adapters/controllers/user.controller.ts
content: // =============================================================================
// User Controller — Clean Architecture Adapter Layer
// =============================================================================
//
// Controllers are primary adapters. They translate HTTP requests into
// use case calls, and use case responses into HTTP responses.
//
// This layer imports from use-cases/ and domain/ — it's allowed to know
// about Express, but business logic stays in the use case.
//
// Key rules:
// - Controllers are THIN — no business logic
// - Extract data from request, call use case, format response
// - Map domain errors to HTTP status codes
// - Never call repositories directly — always go through use cases
//
// Customize: Replace with your own controller. Follow the same pattern.
// =============================================================================

import { Request, Response } from "express";
import {
  CreateUserUseCase,
  UserAlreadyExistsError,
} from "../../use-cases/create-user.use-case";

export class UserController {
  constructor(
    // Controller depends on the use case, not on repositories or entities
    private readonly createUserUseCase: CreateUserUseCase
  ) {}

  /**
   * POST /users
   *
   * The controller does three things:
   * 1. Extracts data from the HTTP request
   * 2. Calls the use case with a plain DTO
   * 3. Maps the result (or error) to an HTTP response
   */
  async create(req: Request, res: Response): Promise<void> {
    try {
      // 1. Extract — translate HTTP request to use case input
      const result = await this.createUserUseCase.execute({
        email: req.body.email,
        name: req.body.name,
      });

      // 2. Respond — translate use case output to HTTP response
      res.status(201).json(result);
    } catch (error) {
      // 3. Error mapping — translate domain errors to HTTP status codes
      if (error instanceof UserAlreadyExistsError) {
        res.status(409).json({ error: error.message });
        return;
      }

      // Unexpected errors — let Express error middleware handle them
      throw error;
    }
  }
}


path: templates/ddd/README.md
content: # =============================================================================
# Domain-Driven Design — Project Scaffold
# =============================================================================
#
# Tactical DDD patterns: value objects, entities, aggregates,
# repositories, domain events, and domain services.
#
# Usage:
#   Copy this directory as your project starting point.
#   Replace the example Order aggregate with your own bounded context.
#
# Core Principles:
#   - Entities have identity, value objects have equality by content
#   - Aggregates are consistency boundaries — all changes through the root
#   - Repositories exist for aggregate roots only
#   - Domain events decouple aggregates and bounded contexts
#
# =============================================================================

## Directory Structure

```
src/
├── domain/                         # Core domain model
│   ├── aggregates/                 # Aggregate roots with their children
│   │   ├── order.aggregate.ts      # Example: Order aggregate root
│   │   └── order-item.entity.ts    # Child entity (managed by Order)
│   ├── entities/                   # Standalone entities (if any)
│   │   └── customer.entity.ts
│   ├── value-objects/              # Immutable domain concepts
│   │   ├── money.value-object.ts   # Example: Money value object
│   │   ├── order-id.value-object.ts
│   │   ├── email.value-object.ts
│   │   └── quantity.value-object.ts
│   ├── events/                     # Domain events (past tense)
│   │   ├── order-submitted.event.ts # Example: OrderSubmitted event
│   │   └── order-cancelled.event.ts
│   ├── services/                   # Domain services (cross-aggregate logic)
│   │   └── pricing.service.ts
│   ├── repositories/               # Repository interfaces (ports)
│   │   └── order.repository.ts
│   └── errors/                     # Domain-specific errors
│       └── domain.errors.ts
│
├── application/                    # Application services (use cases)
│   ├── submit-order.use-case.ts
│   ├── cancel-order.use-case.ts
│   └── event-handlers/             # React to domain events
│       ├── send-confirmation.handler.ts
│       └── update-inventory.handler.ts
│
├── infrastructure/                 # Technical implementations
│   ├── persistence/                # Repository implementations
│   │   └── prisma-order.repository.ts
│   ├── messaging/                  # Event bus implementation
│   │   └── event-dispatcher.ts
│   └── composition-root.ts        # Dependency wiring
│
└── main.ts                         # Entry point
```

## Customize

1. **Identify your aggregates** — What are your consistency boundaries?
2. **Define value objects** — What concepts need validation and immutability?
3. **Model aggregate roots** — All external access goes through the root
4. **Define domain events** — What happenings need to propagate?
5. **Create repository interfaces** — One per aggregate root
6. **Implement application services** — Orchestrate aggregates and services

## Key Rules

- Aggregates reference other aggregates by **ID**, not by object
- All changes to an aggregate go through the **aggregate root**
- Repository interfaces are in `domain/` — implementations in `infrastructure/`
- Domain events are collected during mutation, published after persistence
- Value objects are **immutable** — operations return new instances
- One repository per **aggregate root** — child entities saved through parent


path: templates/ddd/src/domain/events/order-submitted.event.ts
content: // =============================================================================
// OrderSubmitted Domain Event — DDD Tactical Pattern
// =============================================================================
//
// Domain events represent something that happened in the domain.
// They are named in past tense and carry enough data for handlers
// to react without querying back to the source aggregate.
//
// Key rules:
// - Past tense name: OrderSubmitted, not SubmitOrder
// - Immutable — created once, never modified
// - Self-describing — handlers shouldn't need to query the DB
// - Collected by the aggregate during mutation
// - Published after the aggregate is persisted (not during mutation)
//
// Customize: Create events for significant state changes in your domain.
// =============================================================================

/**
 * Base class for all domain events.
 * Provides a timestamp and requires an event type identifier.
 */
export abstract class DomainEvent {
  /** When the event occurred. Set automatically at creation. */
  public readonly occurredAt: Date = new Date();

  /** Unique event type string for dispatching. */
  abstract readonly eventType: string;
}

/**
 * Raised when an order is submitted by a customer.
 *
 * Handlers might:
 * - Send a confirmation email (SendConfirmationHandler)
 * - Reserve inventory (UpdateInventoryHandler)
 * - Create an invoice (CreateInvoiceHandler)
 * - Notify the warehouse (NotifyWarehouseHandler)
 *
 * Each handler is independent — one handler's failure
 * should not prevent others from running.
 */
export class OrderSubmittedEvent extends DomainEvent {
  readonly eventType = "order.submitted";

  constructor(
    /** The order that was submitted. */
    public readonly orderId: string,
    /** The customer who submitted the order. */
    public readonly customerId: string,
    /** Total amount — handlers don't need to query the order. */
    public readonly totalAmount: number,
    /** Currency of the total amount. */
    public readonly currency: string,
    /** Number of items — useful for warehouse notification. */
    public readonly itemCount: number
  ) {
    super();
  }
}


path: templates/ddd/src/domain/aggregates/order.aggregate.ts
content: // =============================================================================
// Order Aggregate — DDD Tactical Pattern
// =============================================================================
//
// The aggregate root is the entry point for all modifications. External code
// NEVER reaches into child entities (OrderItem) directly.
//
// Key rules:
// - All changes go through the aggregate root's public methods
// - The root enforces invariants across its children
// - Child entities are exposed as readonly (no external mutation)
// - Domain events are collected during operations, published after persistence
// - Other aggregates are referenced by ID, not by object
//
// Customize: Replace with your own aggregate. Follow the same structure.
// =============================================================================

import { Money } from "../value-objects/money.value-object";

// ---------- Domain event base ----------

abstract class DomainEvent {
  public readonly occurredAt: Date = new Date();
  abstract readonly eventType: string;
}

// ---------- Child entity (managed by the aggregate) ----------

class OrderItem {
  constructor(
    public readonly productId: string,
    private _quantity: number,
    private _unitPrice: Money
  ) {
    if (_quantity < 1) {
      throw new Error("Quantity must be at least 1");
    }
  }

  get quantity(): number {
    return this._quantity;
  }

  get lineTotal(): Money {
    return this._unitPrice.multiply(this._quantity);
  }

  /** Only callable within the aggregate — not exposed externally. */
  updateQuantity(newQuantity: number): void {
    if (newQuantity < 1) {
      throw new Error("Quantity must be at least 1");
    }
    this._quantity = newQuantity;
  }
}

// ---------- Aggregate root ----------

type OrderStatus = "draft" | "submitted" | "cancelled";

export class Order {
  private _domainEvents: DomainEvent[] = [];

  private constructor(
    public readonly id: string,
    private readonly _customerId: string,  // ID reference to Customer aggregate
    private _items: OrderItem[],
    private _status: OrderStatus,
    private _submittedAt: Date | null
  ) {}

  // ---------- Factory methods ----------

  /** Create a new draft order. */
  static create(customerId: string): Order {
    return new Order(crypto.randomUUID(), customerId, [], "draft", null);
  }

  /** Reconstitute from persistence (used by repository mapper). */
  static reconstitute(props: {
    id: string;
    customerId: string;
    items: Array<{ productId: string; quantity: number; unitPrice: Money }>;
    status: OrderStatus;
    submittedAt: Date | null;
  }): Order {
    const items = props.items.map(
      (i) => new OrderItem(i.productId, i.quantity, i.unitPrice)
    );
    return new Order(props.id, props.customerId, items, props.status, props.submittedAt);
  }

  // ---------- Getters (readonly access to internals) ----------

  get customerId(): string {
    return this._customerId;
  }

  get status(): OrderStatus {
    return this._status;
  }

  get submittedAt(): Date | null {
    return this._submittedAt;
  }

  /** Items exposed as readonly — external code cannot mutate them. */
  get items(): ReadonlyArray<{ productId: string; quantity: number; lineTotal: Money }> {
    return this._items.map((item) => ({
      productId: item.productId,
      quantity: item.quantity,
      lineTotal: item.lineTotal,
    }));
  }

  /** Calculate total across all items — aggregate-level invariant. */
  get total(): Money {
    if (this._items.length === 0) {
      return Money.zero("USD");
    }
    return this._items.reduce(
      (sum, item) => sum.add(item.lineTotal),
      Money.zero("USD")
    );
  }

  // ---------- Domain events ----------

  get domainEvents(): ReadonlyArray<DomainEvent> {
    return [...this._domainEvents];
  }

  clearEvents(): void {
    this._domainEvents = [];
  }

  // ---------- Business behavior (all changes through the root) ----------

  /** Add an item to the order. Only allowed in draft status. */
  addItem(productId: string, quantity: number, unitPrice: Money): void {
    this.assertDraft("add items");

    // Check if product already in order — aggregate enforces uniqueness
    const existing = this._items.find((i) => i.productId === productId);
    if (existing) {
      existing.updateQuantity(existing.quantity + quantity);
    } else {
      this._items.push(new OrderItem(productId, quantity, unitPrice));
    }
  }

  /** Remove an item from the order. Only allowed in draft status. */
  removeItem(productId: string): void {
    this.assertDraft("remove items");

    const index = this._items.findIndex((i) => i.productId === productId);
    if (index === -1) {
      throw new Error(`Product ${productId} not found in order ${this.id}`);
    }
    this._items.splice(index, 1);
  }

  /** Submit the order. Enforces non-empty invariant. Raises domain event. */
  submit(): void {
    this.assertDraft("submit");

    if (this._items.length === 0) {
      throw new Error(`Cannot submit empty order ${this.id}`);
    }

    // State transition
    this._status = "submitted";
    this._submittedAt = new Date();

    // Collect domain event — published after persistence
    this._domainEvents.push(
      new OrderSubmittedEvent(this.id, this._customerId, this.total)
    );
  }

  /** Cancel the order. Only draft and submitted orders can be cancelled. */
  cancel(reason: string): void {
    if (this._status === "cancelled") {
      throw new Error(`Order ${this.id} is already cancelled`);
    }

    this._status = "cancelled";

    this._domainEvents.push(
      new OrderCancelledEvent(this.id, this._customerId, reason)
    );
  }

  // ---------- Internal invariant enforcement ----------

  private assertDraft(action: string): void {
    if (this._status !== "draft") {
      throw new Error(`Cannot ${action} on order ${this.id} in status: ${this._status}`);
    }
  }
}

// ---------- Domain events (past tense — something that happened) ----------

class OrderSubmittedEvent extends DomainEvent {
  readonly eventType = "order.submitted";

  constructor(
    public readonly orderId: string,
    public readonly customerId: string,
    public readonly totalAmount: Money
  ) {
    super();
  }
}

class OrderCancelledEvent extends DomainEvent {
  readonly eventType = "order.cancelled";

  constructor(
    public readonly orderId: string,
    public readonly customerId: string,
    public readonly reason: string
  ) {
    super();
  }
}


path: templates/ddd/src/domain/value-objects/money.value-object.ts
content: // =============================================================================
// Money Value Object — DDD Tactical Pattern
// =============================================================================
//
// Value objects are immutable, identity-less, and validated on creation.
// Two Money instances with the same amount and currency are equal.
//
// Key rules:
// - Private constructor — creation only through static factory
// - Validation at creation — a Money instance is always valid
// - Immutable — all operations return NEW instances
// - Equality by value — compared by amount and currency, not by reference
//
// Customize: Adjust currencies, precision, and operations for your domain.
// =============================================================================

export class Money {
  // Private constructor forces creation through factory method
  private constructor(
    public readonly amount: number,
    public readonly currency: string
  ) {}

  // ---------- Factory ----------

  /** Create a Money value. Validates amount and currency. */
  static of(amount: number, currency: string): Money {
    if (!Number.isFinite(amount)) {
      throw new Error(`Money amount must be finite, got: ${amount}`);
    }
    if (amount < 0) {
      throw new Error(`Money amount must be non-negative, got: ${amount}`);
    }
    const allowed = ["USD", "EUR", "GBP"];
    if (!allowed.includes(currency)) {
      throw new Error(`Unsupported currency: ${currency}`);
    }
    // Round to 2 decimal places to avoid floating point issues
    return new Money(Math.round(amount * 100) / 100, currency);
  }

  /** Zero amount for a given currency. */
  static zero(currency: string): Money {
    return Money.of(0, currency);
  }

  // ---------- Arithmetic (returns new instances — immutable) ----------

  add(other: Money): Money {
    this.assertSameCurrency(other);
    return Money.of(this.amount + other.amount, this.currency);
  }

  subtract(other: Money): Money {
    this.assertSameCurrency(other);
    return Money.of(this.amount - other.amount, this.currency);
  }

  multiply(factor: number): Money {
    return Money.of(this.amount * factor, this.currency);
  }

  // ---------- Comparison ----------

  isGreaterThan(other: Money): boolean {
    this.assertSameCurrency(other);
    return this.amount > other.amount;
  }

  isZero(): boolean {
    return this.amount === 0;
  }

  // ---------- Equality (by value, not identity) ----------

  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency;
  }

  // ---------- Conversion ----------

  /** Convert to cents/pence for payment APIs (e.g., Stripe). */
  toCents(): number {
    return Math.round(this.amount * 100);
  }

  toString(): string {
    return `${this.currency} ${this.amount.toFixed(2)}`;
  }

  // ---------- Internal ----------

  private assertSameCurrency(other: Money): void {
    if (this.currency !== other.currency) {
      throw new Error(
        `Cannot operate on different currencies: ${this.currency} vs ${other.currency}`
      );
    }
  }
}


path: templates/hexagonal/README.md
content: # =============================================================================
# Hexagonal Architecture (Ports & Adapters) - Project Scaffold
# =============================================================================
#
# The domain core communicates with the outside world exclusively through
# ports (interfaces). Adapters connect external technologies to these ports.
#
# Usage:
#   Copy this directory as your project starting point.
#   Replace the example Order domain with your own business concepts.
#
# Key Distinction:
#   Primary (Driving) — external world drives the app (HTTP, CLI, events)
#   Secondary (Driven) — app drives external services (DB, payment, email)
#
# =============================================================================

## Directory Structure

```
src/
├── domain/                         # Core business logic (the hexagon)
│   ├── order.service.ts            # Example: Domain service
│   ├── entities/                   # Business entities with behavior
│   │   └── order.entity.ts
│   ├── value-objects/              # Immutable domain concepts
│   │   └── money.value-object.ts
│   ├── errors/                     # Domain-specific errors
│   │   └── domain.errors.ts
│   └── events/                     # Domain events
│       └── order-submitted.event.ts
│
├── ports/                          # Interfaces (hexagon edges)
│   ├── inbound/                    # Primary ports — USE CASE interfaces
│   │   ├── create-order.port.ts    # What the app can do
│   │   └── get-order.port.ts
│   └── outbound/                   # Secondary ports — DEPENDENCY interfaces
│       ├── order-repository.port.ts    # What the app needs from persistence
│       └── payment-gateway.port.ts     # What the app needs from payments
│
├── adapters/                       # Implementations (outside the hexagon)
│   ├── primary/                    # Driving adapters — call inbound ports
│   │   ├── rest/                   # REST API adapter
│   │   │   └── order.controller.ts
│   │   ├── cli/                    # CLI adapter (same port, different trigger)
│   │   │   └── order.command.ts
│   │   └── graphql/                # GraphQL adapter
│   │       └── order.resolver.ts
│   └── secondary/                  # Driven adapters — implement outbound ports
│       ├── persistence/            # Database adapter
│       │   └── prisma-order.repository.ts
│       ├── payment/                # Payment processor adapter
│       │   └── stripe-payment.adapter.ts
│       └── notification/           # Notification adapter
│           └── email-notifier.adapter.ts
│
├── infrastructure/                 # Framework & wiring
│   ├── config/                     # Environment configuration
│   └── composition-root.ts        # Wires adapters to ports
│
└── main.ts                         # Entry point
```

## Customize

1. **Define domain entities** — Your core business objects in `domain/entities/`
2. **Create inbound ports** — Use case interfaces in `ports/inbound/`
3. **Create outbound ports** — Dependency interfaces in `ports/outbound/`
4. **Implement domain logic** — Use cases in `domain/` that implement inbound ports
5. **Build primary adapters** — REST, CLI, or GraphQL adapters in `adapters/primary/`
6. **Build secondary adapters** — DB, payment, email adapters in `adapters/secondary/`
7. **Wire in composition root** — Connect everything in `infrastructure/composition-root.ts`

## Key Rules

- `domain/` imports from: `ports/` only (interfaces it implements or depends on)
- `ports/` imports from: `domain/` types only (entities, value objects in signatures)
- `adapters/primary/` imports from: `ports/inbound/` (calls the port)
- `adapters/secondary/` imports from: `ports/outbound/` (implements the port)
- Adapters NEVER import other adapters
- Business logic NEVER appears in adapters


path: templates/hexagonal/src/domain/order.service.ts
content: // =============================================================================
// Order Service — Hexagonal Architecture Domain Layer
// =============================================================================
//
// This domain service implements the inbound (primary) port.
// It contains business logic and depends on outbound (secondary) ports
// for persistence and external services.
//
// The service knows WHAT to do. Adapters know HOW.
//
// Key rules:
// - Implements a primary port interface
// - Depends on secondary port interfaces (not implementations)
// - Contains business logic — no framework or infrastructure code
// - Dependencies injected through constructor
//
// Customize: Replace with your own domain service.
// =============================================================================

// Import port interfaces — the service depends on abstractions
// import { CreateOrderPort } from '../ports/inbound/create-order.port';
// import { OrderRepository } from '../ports/outbound/order-repository.port';
// import { PaymentGateway } from '../ports/outbound/payment-gateway.port';

// ---------- Port interfaces (inline for this example) ----------

interface OrderRepository {
  findById(id: string): Promise<Order | null>;
  save(order: Order): Promise<void>;
}

interface PaymentGateway {
  charge(request: ChargeRequest): Promise<ChargeResult>;
}

interface ChargeRequest {
  orderId: string;
  amount: number;
  currency: string;
  paymentMethodId: string;
}

interface ChargeResult {
  paymentId: string;
  status: "succeeded" | "failed";
}

// ---------- Domain entity (simplified) ----------

interface OrderItem {
  productId: string;
  quantity: number;
  unitPrice: number;
}

class Order {
  constructor(
    public readonly id: string,
    public readonly customerId: string,
    private _items: OrderItem[],
    private _status: "draft" | "submitted" | "paid" | "cancelled"
  ) {}

  static create(customerId: string, items: OrderItem[]): Order {
    if (items.length === 0) {
      throw new Error("Order must have at least one item");
    }
    return new Order(crypto.randomUUID(), customerId, items, "draft");
  }

  get total(): number {
    return this._items.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0);
  }

  get status(): string {
    return this._status;
  }

  get items(): ReadonlyArray<OrderItem> {
    return [...this._items];
  }

  submit(): void {
    if (this._status !== "draft") {
      throw new Error(`Cannot submit order in status: ${this._status}`);
    }
    this._status = "submitted";
  }

  markPaid(): void {
    if (this._status !== "submitted") {
      throw new Error(`Cannot mark as paid in status: ${this._status}`);
    }
    this._status = "paid";
  }
}

// ---------- Domain service ----------

export class OrderService {
  constructor(
    private readonly orderRepository: OrderRepository,
    private readonly paymentGateway: PaymentGateway
  ) {}

  /**
   * Create and submit an order, then process payment.
   *
   * This method orchestrates domain logic:
   * 1. Creates the order entity (entity validates itself)
   * 2. Submits the order (entity enforces valid transition)
   * 3. Charges payment through the gateway port
   * 4. Persists the result through the repository port
   */
  async createAndPayOrder(
    customerId: string,
    items: OrderItem[],
    paymentMethodId: string
  ): Promise<{ orderId: string; paymentId: string }> {
    // Domain entity handles its own validation
    const order = Order.create(customerId, items);
    order.submit();

    // Charge through the payment gateway port
    // The actual payment processor (Stripe, PayPal) is an adapter concern
    const chargeResult = await this.paymentGateway.charge({
      orderId: order.id,
      amount: order.total,
      currency: "USD",
      paymentMethodId,
    });

    if (chargeResult.status === "failed") {
      throw new Error(`Payment failed for order ${order.id}`);
    }

    order.markPaid();

    // Persist through the repository port
    // The actual database (Postgres, MongoDB) is an adapter concern
    await this.orderRepository.save(order);

    return {
      orderId: order.id,
      paymentId: chargeResult.paymentId,
    };
  }
}


path: templates/hexagonal/src/ports/outbound/payment-gateway.port.ts
content: // =============================================================================
// Payment Gateway Port — Hexagonal Architecture Outbound Port
// =============================================================================
//
// This is a SECONDARY (outbound/driven) port. It defines what the domain
// needs from a payment processor, using domain vocabulary.
//
// The interface knows nothing about Stripe, PayPal, or any specific vendor.
// Adapters in adapters/secondary/ implement this interface with vendor SDKs.
//
// Key rules:
// - Uses domain types (Money, OrderId), not vendor types
// - Defines operations the domain needs, not what the vendor offers
// - Error handling uses domain errors, not vendor exceptions
//
// Customize: Adapt the interface to your payment requirements.
// =============================================================================

export interface ChargeRequest {
  orderId: string;
  amount: number;
  currency: string;
  paymentMethodId: string;
  description?: string;
}

export interface ChargeResult {
  paymentId: string;
  status: "succeeded" | "failed" | "pending";
  failureReason?: string;
}

export interface RefundRequest {
  paymentId: string;
  amount: number;
  reason?: string;
}

export interface RefundResult {
  refundId: string;
  status: "succeeded" | "failed" | "pending";
}

/**
 * Payment gateway port — defines what the domain needs from payment processing.
 *
 * Implementations:
 * - StripePaymentAdapter (production)
 * - FakePaymentGateway (testing — always succeeds)
 * - FailingPaymentGateway (testing — always fails)
 */
export interface PaymentGateway {
  /** Charge a payment method for an order. */
  charge(request: ChargeRequest): Promise<ChargeResult>;

  /** Refund a previous charge (partial or full). */
  refund(request: RefundRequest): Promise<RefundResult>;
}


path: templates/hexagonal/src/adapters/secondary/stripe-payment.adapter.ts
content: // =============================================================================
// Stripe Payment Adapter — Hexagonal Architecture Secondary Adapter
// =============================================================================
//
// This is a SECONDARY (driven) adapter. It implements the PaymentGateway
// port using the Stripe SDK.
//
// The adapter translates between:
// - Domain vocabulary (ChargeRequest, ChargeResult) ← port types
// - Vendor vocabulary (Stripe.Charge, Stripe.ChargeCreateParams) ← SDK types
//
// Key rules:
// - Implements the port interface exactly
// - Translates vendor errors to domain errors
// - Contains NO business logic — only translation
// - Stripe SDK is imported HERE, never in domain or ports
//
// Customize: Replace with your payment provider's SDK.
// =============================================================================

import Stripe from "stripe";
import {
  PaymentGateway,
  ChargeRequest,
  ChargeResult,
  RefundRequest,
  RefundResult,
} from "../../ports/outbound/payment-gateway.port";

export class StripePaymentAdapter implements PaymentGateway {
  private readonly stripe: Stripe;

  constructor([REDACTED_SECRET]) {
    this.stripe = new Stripe(apiKey);
  }

  async charge(request: ChargeRequest): Promise<ChargeResult> {
    try {
      // Translate domain request → Stripe API call
      const paymentIntent = await this.stripe.paymentIntents.create({
        amount: Math.round(request.amount * 100), // Stripe uses cents
        currency: request.currency.toLowerCase(),
        payment_method: request.paymentMethodId,
        confirm: true,
        automatic_payment_methods: {
          enabled: true,
          allow_redirects: "never",
        },
        metadata: {
          orderId: request.orderId,
        },
        description: request.description,
      });

      // Translate Stripe response → domain result
      return {
        paymentId: paymentIntent.id,
        status: paymentIntent.status === "succeeded" ? "succeeded" : "pending",
      };
    } catch (error) {
      // Translate Stripe errors → domain result (not Stripe exceptions)
      if (error instanceof Stripe.errors.StripeCardError) {
        return {
          paymentId: "",
          status: "failed",
          failureReason: error.message,
        };
      }

      // Unexpected Stripe errors become domain-level payment errors
      throw new PaymentProcessingError(
        `Payment processing failed: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }

  async refund(request: RefundRequest): Promise<RefundResult> {
    try {
      const refund = await this.stripe.refunds.create({
        payment_intent: request.paymentId,
        amount: Math.round(request.amount * 100),
        reason: request.reason as Stripe.RefundCreateParams.Reason,
      });

      return {
        refundId: refund.id,
        status: refund.status === "succeeded" ? "succeeded" : "pending",
      };
    } catch (error) {
      throw new PaymentProcessingError(
        `Refund processing failed: ${error instanceof Error ? error.message : "Unknown error"}`
      );
    }
  }
}

// ---------- Domain error (could live in domain/errors/) ----------

class PaymentProcessingError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "PaymentProcessingError";
  }
}


path: scripts/search.py
content: #!/usr/bin/env python3
"""Search architecture patterns from the patterns CSV database.

Usage:
    python search.py "microservices"           # keyword search
    python search.py --category "Resilience"   # by category (partial match)
    python search.py --complexity low           # by complexity level
    python search.py --scale enterprise         # by scale
    python search.py --list-categories          # show all categories
    python search.py --all                      # show all patterns
"""

import argparse
import csv
import os
import sys
from typing import List, Dict

DATA_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "data", "patterns.csv")

VALID_COMPLEXITY = {"low", "medium", "high"}
VALID_SCALE = {"small", "medium", "large", "enterprise"}

HEADER_ID = "ID"
HEADER_NAME = "Name"
HEADER_CATEGORY = "Category"
HEADER_COMPLEXITY = "Cplx"
HEADER_SCALE = "Scale"


def load_patterns() -> List[Dict[str, str]]:
    """Load patterns from the CSV file."""
    if not os.path.exists(DATA_FILE):
        print(f"Error: Data file not found at {DATA_FILE}", file=sys.stderr)
        sys.exit(1)

    patterns = []
    with open(DATA_FILE, newline="", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            patterns.append(row)
    return patterns


def search_keyword(patterns: List[Dict[str, str]], keyword: str) -> List[Dict[str, str]]:
    """Search patterns by keyword across all text fields."""
    keyword_lower = keyword.lower()
    results = []
    searchable_fields = ["name", "category", "description", "use_case", "tradeoffs"]
    for p in patterns:
        for field in searchable_fields:
            if keyword_lower in p.get(field, "").lower():
                results.append(p)
                break
    return results


def filter_by_field(patterns: List[Dict[str, str]], field: str, value: str) -> List[Dict[str, str]]:
    """Filter patterns by exact or partial match on a field."""
    value_lower = value.lower()
    return [p for p in patterns if value_lower in p.get(field, "").lower()]


def get_categories(patterns: List[Dict[str, str]]) -> List[str]:
    """Get sorted unique categories."""
    categories = sorted(set(p["category"] for p in patterns))
    return categories


def format_table(patterns: List[Dict[str, str]]) -> str:
    """Format patterns as an aligned table."""
    if not patterns:
        return "  No patterns found."

    col_id = max(len(HEADER_ID), max(len(p["id"]) for p in patterns))
    col_name = max(len(HEADER_NAME), max(len(p["name"]) for p in patterns))
    col_cat = max(len(HEADER_CATEGORY), max(len(p["category"]) for p in patterns))
    col_cplx = max(len(HEADER_COMPLEXITY), max(len(p["complexity"]) for p in patterns))
    col_scale = max(len(HEADER_SCALE), max(len(p["scale"]) for p in patterns))

    header = (
        f"  {HEADER_ID:>{col_id}}  {HEADER_NAME:<{col_name}}  "
        f"{HEADER_CATEGORY:<{col_cat}}  {HEADER_COMPLEXITY:<{col_cplx}}  {HEADER_SCALE:<{col_scale}}"
    )
    sep = "  " + "-" * (col_id + col_name + col_cat + col_cplx + col_scale + 8)

    lines = [header, sep]
    for p in patterns:
        line = (
            f"  {p['id']:>{col_id}}  {p['name']:<{col_name}}  "
            f"{p['category']:<{col_cat}}  {p['complexity']:<{col_cplx}}  {p['scale']:<{col_scale}}"
        )
        lines.append(line)

    return "\n".join(lines)


def print_detail(pattern: Dict[str, str]) -> None:
    """Print detailed view of a single pattern."""
    print(f"\n  [{pattern['id']}] {pattern['name']}")
    print(f"  Category:   {pattern['category']}")
    print(f"  Complexity: {pattern['complexity']}")
    print(f"  Scale:      {pattern['scale']}")
    print(f"  Description: {pattern['description']}")
    print(f"  Use case:    {pattern['use_case']}")
    print(f"  Tradeoffs:   {pattern['tradeoffs']}")


def print_results(patterns: List[Dict[str, str]], detail: bool = False) -> None:
    """Print search results."""
    count = len(patterns)
    label = "pattern" if count == 1 else "patterns"
    print(f"\n  Found {count} {label}:\n")

    if detail and count <= 5:
        for p in patterns:
            print_detail(p)
        print()
    else:
        print(format_table(patterns))
        print()


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Search architecture patterns database.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )
    parser.add_argument("keyword", nargs="?", help="Keyword to search across all fields")
    parser.add_argument("--category", "-c", help="Filter by category (partial match)")
    parser.add_argument(
        "--complexity", "-x",
        choices=sorted(VALID_COMPLEXITY),
        help="Filter by complexity level",
    )
    parser.add_argument(
        "--scale", "-s",
        choices=sorted(VALID_SCALE),
        help="Filter by scale",
    )
    parser.add_argument("--list-categories", "-l", action="store_true", help="List all categories")
    parser.add_argument("--detail", "-d", action="store_true", help="Show detailed output (auto for <=5 results)")
    parser.add_argument("--all", "-a", action="store_true", help="Show all patterns")

    args = parser.parse_args()

    if not any([args.keyword, args.category, args.complexity, args.scale, args.list_categories, args.all]):
        parser.print_help()
        sys.exit(0)

    patterns = load_patterns()

    if args.list_categories:
        categories = get_categories(patterns)
        print("\n  Categories:\n")
        for cat in categories:
            count = sum(1 for p in patterns if p["category"] == cat)
            print(f"    - {cat} ({count} patterns)")
        print()
        return

    results = patterns

    if args.keyword:
        results = search_keyword(results, args.keyword)
    if args.category:
        results = filter_by_field(results, "category", args.category)
    if args.complexity:
        results = filter_by_field(results, "complexity", args.complexity)
    if args.scale:
        results = filter_by_field(results, "scale", args.scale)

    show_detail = args.detail or len(results) <= 3
    print_results(results, detail=show_detail)


if __name__ == "__main__":
    main()


path: references/ddd-tactical-patterns.md
content: # DDD Tactical Patterns

Value objects, entities, aggregates, repositories, domain events, and domain services implemented in TypeScript. These are the building blocks of a domain-driven design.

---

## Value Objects

Value objects represent descriptive, immutable concepts with no identity. Two value objects with the same attributes are considered equal.

### Pattern 1: Immutable Value Object

Value objects are created with validation and cannot be modified. Operations return new instances.

❌ **Bad: Mutable plain object used as currency amount**

```typescript
// No validation, mutable, no behavior
interface Money {
  amount: number;
  currency: string;
}

function createPayment(money: Money) {
  money.amount = money.amount * 1.1; // Mutated — caller affected
  // No guarantee amount is positive or currency is valid
}
```

✅ **Good: Immutable value object with validation**

```typescript
class Money {
  private constructor(
    public readonly amount: number,
    public readonly currency: string
  ) {}

  static of(amount: number, currency: string): Money {
    if (!Number.isFinite(amount)) {
      throw new InvalidMoneyError(`Amount must be finite: ${amount}`);
    }
    if (amount < 0) {
      throw new InvalidMoneyError(`Amount must be non-negative: ${amount}`);
    }
    const validCurrencies = ["USD", "EUR", "GBP", "JPY"];
    if (!validCurrencies.includes(currency)) {
      throw new InvalidMoneyError(`Invalid currency: ${currency}`);
    }
    return new Money(amount, currency);
  }

  add(other: Money): Money {
    this.assertSameCurrency(other);
    return Money.of(this.amount + other.amount, this.currency);
  }

  subtract(other: Money): Money {
    this.assertSameCurrency(other);
    if (this.amount < other.amount) {
      throw new InsufficientFundsError(this, other);
    }
    return Money.of(this.amount - other.amount, this.currency);
  }

  multiply(factor: number): Money {
    return Money.of(Math.round(this.amount * factor * 100) / 100, this.currency);
  }

  isGreaterThan(other: Money): boolean {
    this.assertSameCurrency(other);
    return this.amount > other.amount;
  }

  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency;
  }

  toCents(): number {
    return Math.round(this.amount * 100);
  }

  toString(): string {
    return `${this.currency} ${this.amount.toFixed(2)}`;
  }

  private assertSameCurrency(other: Money): void {
    if (this.currency !== other.currency) {
      throw new CurrencyMismatchError(this.currency, other.currency);
    }
  }
}
```

**Rule:** Value objects are immutable. All operations return new instances. Validation happens at creation — a value object is always valid by construction.

---

### Pattern 2: Value Object with Behavior

Value objects encapsulate domain logic related to the concept they represent.

```typescript
class DateRange {
  private constructor(
    public readonly start: Date,
    public readonly end: Date
  ) {}

  static create(start: Date, end: Date): DateRange {
    if (end <= start) {
      throw new InvalidDateRangeError("End must be after start");
    }
    return new DateRange(start, end);
  }

  contains(date: Date): boolean {
    return date >= this.start && date <= this.end;
  }

  overlaps(other: DateRange): boolean {
    return this.start < other.end && this.end > other.start;
  }

  get durationInDays(): number {
    const ms = this.end.getTime() - this.start.getTime();
    return Math.ceil(ms / (1000 * 60 * 60 * 24));
  }

  extend(days: number): DateRange {
    const newEnd = new Date(this.end);
    newEnd.setDate(newEnd.getDate() + days);
    return DateRange.create(this.start, newEnd);
  }

  equals(other: DateRange): boolean {
    return (
      this.start.getTime() === other.start.getTime() &&
      this.end.getTime() === other.end.getTime()
    );
  }
}
```

**Rule:** Value objects are not just data containers. They encapsulate logic that belongs to the concept — date ranges know about overlap, money knows about arithmetic, addresses know about formatting.

---

### Pattern 3: Replacing Primitives

Replace primitive types with value objects when the primitive has validation rules, formatting, or risk of being confused with other values of the same type.

❌ **Bad: Primitive types everywhere — easy to mix up**

```typescript
class OrderService {
  async createOrder(
    userId: string,      // Which string is which?
    productId: string,   // Easy to swap arguments
    quantity: number,     // Could be negative
    price: number         // Could be NaN
  ): Promise<string> {   // What does the return string represent?
    // ...
  }
}

// Bug: arguments swapped — compiles fine, fails at runtime
orderService.createOrder(productId, userId, price, quantity);
```

✅ **Good: Value objects prevent mix-ups at compile time**

```typescript
class UserId {
  private constructor(private readonly value: string) {}
  static from(value: string): UserId {
    if (!value || value.trim().length === 0) throw new Error("Invalid user ID");
    return new UserId(value);
  }
  equals(other: UserId): boolean { return this.value === other.value; }
  toString(): string { return this.value; }
}

class ProductId {
  private constructor(private readonly value: string) {}
  static from(value: string): ProductId {
    if (!value) throw new Error("Invalid product ID");
    return new ProductId(value);
  }
  equals(other: ProductId): boolean { return this.value === other.value; }
  toString(): string { return this.value; }
}

class Quantity {
  private constructor(public readonly value: number) {}
  static of(value: number): Quantity {
    if (!Number.isInteger(value) || value < 1) {
      throw new Error("Quantity must be a positive integer");
    }
    return new Quantity(value);
  }
}

class OrderService {
  async createOrder(
    userId: UserId,
    productId: ProductId,
    quantity: Quantity,
    price: Money
  ): Promise<OrderId> {
    // Arguments can't be swapped — types prevent it
  }
}
```

**Rule:** Replace primitives with value objects when: (1) the value has validation rules, (2) values of the same type could be confused, or (3) the value has operations or formatting.

---

## Entities

Entities are objects defined by their identity, not their attributes. An entity can change over time but remains the same entity.

### Pattern 4: Entity with Identity

Entities are compared by ID. Two entities with the same ID are the same entity, regardless of their other attributes.

❌ **Bad: No clear identity, compared by attributes**

```typescript
class Customer {
  name: string;
  email: string;

  isSameAs(other: Customer): boolean {
    // Attribute comparison — what if name changes?
    return this.name === other.name && this.email === other.email;
  }
}
```

✅ **Good: Identity-based equality**

```typescript
class Customer {
  constructor(
    public readonly id: CustomerId,
    private _name: string,
    private _email: Email
  ) {}

  // Entities are equal when their IDs match
  equals(other: Customer): boolean {
    return this.id.equals(other.id);
  }

  // Name can change — entity identity is unchanged
  changeName(newName: string): void {
    if (!newName || newName.trim().length === 0) {
      throw new InvalidNameError("Name cannot be empty");
    }
    this._name = newName.trim();
  }

  get name(): string { return this._name; }
  get email(): Email { return this._email; }
}
```

**Rule:** Entity equality is always by ID. Attributes are mutable but identity is permanent. If two objects could have identical attributes but represent different things, they're entities.

---

### Pattern 5: Rich Entity (Anti-Anemic)

Entities encapsulate their behavior. Business rules that govern an entity's state live inside the entity, not in external services.

❌ **Bad: Anemic entity — all logic in services**

```typescript
class Order {
  id: string;
  items: OrderItem[];
  status: string;
  total: number;
}

class OrderService {
  addItem(order: Order, item: OrderItem): void {
    if (order.status !== "draft") throw new Error("Cannot modify");
    order.items.push(item);
    order.total = order.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
  }

  submit(order: Order): void {
    if (order.items.length === 0) throw new Error("Empty order");
    if (order.status !== "draft") throw new Error("Already submitted");
    order.status = "submitted";
  }

  cancel(order: Order): void {
    if (order.status === "shipped") throw new Error("Already shipped");
    order.status = "cancelled";
  }
}
```

✅ **Good: Entity with encapsulated behavior**

```typescript
class Order {
  private constructor(
    public readonly id: OrderId,
    private _items: OrderItem[],
    private _status: OrderStatus,
    private _submittedAt: Date | null
  ) {}

  static create(): Order {
    return new Order(OrderId.generate(), [], OrderStatus.Draft, null);
  }

  addItem(product: Product, quantity: Quantity): void {
    if (!this._status.isDraft()) {
      throw new OrderAlreadySubmittedError(this.id);
    }
    const existingItem = this._items.find((i) => i.productId.equals(product.id));
    if (existingItem) {
      existingItem.increaseQuantity(quantity);
    } else {
      this._items.push(OrderItem.create(product, quantity));
    }
  }

  removeItem(productId: ProductId): void {
    if (!this._status.isDraft()) {
      throw new OrderAlreadySubmittedError(this.id);
    }
    this._items = this._items.filter((i) => !i.productId.equals(productId));
  }

  submit(): void {
    if (this._items.length === 0) {
      throw new EmptyOrderError(this.id);
    }
    if (!this._status.isDraft()) {
      throw new OrderAlreadySubmittedError(this.id);
    }
    this._status = OrderStatus.Submitted;
    this._submittedAt = new Date();
  }

  cancel(): void {
    if (this._status.isShipped()) {
      throw new CannotCancelShippedOrderError(this.id);
    }
    this._status = OrderStatus.Cancelled;
  }

  get total(): Money {
    return this._items.reduce(
      (sum, item) => sum.add(item.lineTotal),
      Money.of(0, "USD")
    );
  }

  get status(): OrderStatus { return this._status; }
  get items(): ReadonlyArray<OrderItem> { return [...this._items]; }
}
```

**Rule:** Ask "who should know this rule?" If the rule is about an entity's own state, it belongs in the entity. Services that just get data, make decisions, and set data back are a sign of an anemic model.

---

### Pattern 6: Entity State Transitions

Model valid state transitions explicitly. The entity enforces which transitions are allowed.

❌ **Bad: Status set directly from outside**

```typescript
class Task {
  status: "todo" | "in_progress" | "done" | "archived";

  setStatus(status: string): void {
    this.status = status; // Any transition allowed — todo → archived? done → todo?
  }
}
```

✅ **Good: Explicit state machine**

```typescript
class TaskStatus {
  private constructor(private readonly value: string) {}

  static readonly Todo = new TaskStatus("todo");
  static readonly InProgress = new TaskStatus("in_progress");
  static readonly Done = new TaskStatus("done");
  static readonly Archived = new TaskStatus("archived");

  private static readonly validTransitions: Record<string, string[]> = {
    todo: ["in_progress"],
    in_progress: ["done", "todo"],
    done: ["archived"],
    archived: [],
  };

  canTransitionTo(target: TaskStatus): boolean {
    return TaskStatus.validTransitions[this.value]?.includes(target.value) ?? false;
  }

  toString(): string { return this.value; }
}

class Task {
  constructor(
    public readonly id: TaskId,
    private _title: string,
    private _status: TaskStatus
  ) {}

  start(): void {
    this.transitionTo(TaskStatus.InProgress);
  }

  complete(): void {
    this.transitionTo(TaskStatus.Done);
  }

  archive(): void {
    this.transitionTo(TaskStatus.Archived);
  }

  private transitionTo(newStatus: TaskStatus): void {
    if (!this._status.canTransitionTo(newStatus)) {
      throw new InvalidTransitionError(this._status, newStatus);
    }
    this._status = newStatus;
  }

  get status(): TaskStatus { return this._status; }
}
```

**Rule:** Don't expose a generic `setStatus()`. Provide named methods for each transition (`start()`, `complete()`, `archive()`). The entity enforces which transitions are valid.

---

## Aggregates

Aggregates are clusters of entities and value objects treated as a single unit for data changes. The aggregate root is the entry point — all external access goes through it.

### Pattern 7: Aggregate Root

All modifications to an aggregate's internals go through the aggregate root. External code never reaches into child entities directly.

❌ **Bad: External code reaching into aggregate internals**

```typescript
// External code modifying an order's item directly
const order = await orderRepo.findById(orderId);
const item = order.items[0];
item.quantity = 5;         // Direct mutation of internal state
item.price = newPrice;     // Bypasses aggregate invariant checks
await orderRepo.save(order);
```

✅ **Good: All changes through the aggregate root**

```typescript
class Order {
  private _items: OrderItem[];

  // External code uses this method — never touches items directly
  updateItemQuantity(productId: ProductId, newQuantity: Quantity): void {
    if (!this._status.isDraft()) {
      throw new OrderAlreadySubmittedError(this.id);
    }

    const item = this._items.find((i) => i.productId.equals(productId));
    if (!item) {
      throw new ItemNotInOrderError(this.id, productId);
    }

    item.updateQuantity(newQuantity); // Internal mutation is OK within aggregate
    this.recalculateTotal();          // Aggregate maintains its invariants
  }

  // Items exposed as readonly to prevent external mutation
  get items(): ReadonlyArray<OrderItem> {
    return [...this._items];
  }
}
```

**Rule:** External code calls methods on the aggregate root. The root coordinates changes to its internals and maintains invariants. Never expose mutable references to child entities.

---

### Pattern 8: Aggregate Boundaries

Aggregates reference other aggregates by ID, not by direct object reference. Keep aggregates small and focused.

❌ **Bad: Giant aggregate containing everything**

```typescript
class Customer {
  id: CustomerId;
  name: string;
  orders: Order[];           // All orders loaded every time
  addresses: Address[];      // All addresses loaded too
  paymentMethods: PaymentMethod[];

  // Loading a customer loads all their orders, addresses, payments...
  // Can't load one order without loading the entire customer
}
```

✅ **Good: Small aggregates with ID references**

```typescript
class Customer {
  constructor(
    public readonly id: CustomerId,
    private _name: string,
    private _email: Email,
    private _defaultAddressId: AddressId | null
  ) {}
  // Customer is small — just identity and contact info
}

class Order {
  constructor(
    public readonly id: OrderId,
    private _customerId: CustomerId,    // ID reference, not object
    private _shippingAddressId: AddressId, // ID reference
    private _items: OrderItem[],
    private _status: OrderStatus
  ) {}
  // Order is its own aggregate — loaded independently
}

class Address {
  constructor(
    public readonly id: AddressId,
    private _customerId: CustomerId,   // ID reference back
    private _street: string,
    private _city: string,
    private _country: Country
  ) {}
}
```

**Rule:** Reference other aggregates by ID, not by object. Each aggregate is loaded independently. If you need data from another aggregate, query its repository.

---

### Pattern 9: Aggregate Invariants

Aggregates enforce business rules (invariants) that span multiple objects within the aggregate boundary.

```typescript
class ShoppingCart {
  private readonly MAX_ITEMS = 50;
  private readonly MAX_QUANTITY_PER_ITEM = 10;

  constructor(
    public readonly id: CartId,
    private _customerId: CustomerId,
    private _items: CartItem[]
  ) {}

  addItem(productId: ProductId, quantity: Quantity, price: Money): void {
    // Invariant: max items in cart
    if (this._items.length >= this.MAX_ITEMS) {
      throw new CartFullError(this.id, this.MAX_ITEMS);
    }

    const existing = this._items.find((i) => i.productId.equals(productId));

    if (existing) {
      const newQuantity = existing.quantity.value + quantity.value;
      // Invariant: max quantity per item
      if (newQuantity > this.MAX_QUANTITY_PER_ITEM) {
        throw new MaxQuantityExceededError(productId, this.MAX_QUANTITY_PER_ITEM);
      }
      existing.updateQuantity(Quantity.of(newQuantity));
    } else {
      this._items.push(CartItem.create(productId, quantity, price));
    }
  }

  // Invariant: cart total (cross-item rule)
  get total(): Money {
    return this._items.reduce(
      (sum, item) => sum.add(item.lineTotal),
      Money.of(0, "USD")
    );
  }
}
```

**Rule:** Invariants that span multiple entities belong in the aggregate root. If a rule only involves one entity, put it in that entity. If it spans entities in different aggregates, use a domain service.

---

### Pattern 10: Aggregate Sizing

Start with small aggregates. Split when you see performance issues, concurrency conflicts, or unrelated concerns bundled together.

❌ **Bad: One aggregate for everything product-related**

```typescript
class Product {
  id: ProductId;
  name: string;
  description: string;
  price: Money;
  inventory: InventoryItem[];  // Stock levels — changes very frequently
  reviews: Review[];           // User reviews — grows unbounded
  images: ProductImage[];      // Rarely changes
  categories: Category[];      // Rarely changes

  // Loading a product loads ALL reviews and inventory
  // Updating inventory locks the entire product aggregate
}
```

✅ **Good: Separate aggregates for separate concerns**

```typescript
// Product aggregate — catalog information
class Product {
  constructor(
    public readonly id: ProductId,
    private _name: string,
    private _description: string,
    private _price: Money,
    private _categoryIds: CategoryId[]
  ) {}
}

// Inventory aggregate — stock levels (changes frequently)
class Inventory {
  constructor(
    public readonly id: InventoryId,
    private _productId: ProductId,   // ID reference
    private _quantity: number,
    private _reservedQuantity: number
  ) {}

  reserve(quantity: number): void {
    if (this.availableQuantity < quantity) {
      throw new InsufficientStockError(this._productId, quantity);
    }
    this._reservedQuantity += quantity;
  }

  get availableQuantity(): number {
    return this._quantity - this._reservedQuantity;
  }
}

// Review aggregate — user content (grows unbounded)
class ProductReview {
  constructor(
    public readonly id: ReviewId,
    private _productId: ProductId,   // ID reference
    private _customerId: CustomerId,
    private _rating: Rating,
    private _content: string
  ) {}
}
```

**Rule:** Split aggregates when: (1) different parts change at different rates, (2) loading the whole aggregate is expensive, (3) concurrent updates cause conflicts, or (4) parts have unrelated business rules.

---

## Repositories

Repositories provide an interface for loading and saving aggregates. They abstract the persistence mechanism from the domain.

### Pattern 11: Repository Contract

Repository interfaces live in the domain layer. They define operations using domain types.

```typescript
// Domain layer — defines the contract
interface OrderRepository {
  findById(id: OrderId): Promise<Order | null>;
  findByCustomer(customerId: CustomerId): Promise<Order[]>;
  findPendingOrders(): Promise<Order[]>;
  save(order: Order): Promise<void>;
  delete(id: OrderId): Promise<void>;
  nextId(): OrderId;
}
```

**Rule:** Repository interfaces use domain types (OrderId, not string). They return domain aggregates (Order, not a database row). The interface knows nothing about SQL, MongoDB, or any persistence technology.

---

### Pattern 12: Repository for Aggregates Only

Repositories exist for aggregate roots only. Child entities and value objects are saved through their parent aggregate.

❌ **Bad: Repository for every entity**

```typescript
// Separate repositories for aggregate root and its children
interface OrderRepository { save(order: Order): Promise<void>; }
interface OrderItemRepository { save(item: OrderItem): Promise<void>; }
interface ShippingInfoRepository { save(info: ShippingInfo): Promise<void>; }

// External code saves parts independently — aggregate invariants bypassed
await orderItemRepo.save(newItem);  // No aggregate validation
```

✅ **Good: Repository for aggregate root only**

```typescript
interface OrderRepository {
  findById(id: OrderId): Promise<Order | null>;
  save(order: Order): Promise<void>;
  // Saves the entire aggregate: order + items + shipping info
}

// The repository implementation handles child entities internally
class PrismaOrderRepository implements OrderRepository {
  async save(order: Order): Promise<void> {
    await this.prisma.$transaction(async (tx) => {
      // Save the aggregate root
      await tx.order.upsert({ where: { id: order.id.toString() }, /* ... */ });

      // Save child entities as part of the same transaction
      await tx.orderItem.deleteMany({ where: { orderId: order.id.toString() } });
      await tx.orderItem.createMany({
        data: order.items.map((item) => this.toItemPersistence(item, order.id)),
      });
    });
  }
}
```

**Rule:** One repository per aggregate root. Child entities are persisted through the aggregate's repository. This ensures the aggregate is always saved as a consistent whole.

---

### Pattern 13: Specification Pattern

Encapsulate complex query criteria as domain objects. This keeps query logic testable and composable.

```typescript
// Specification interface
interface Specification<T> {
  isSatisfiedBy(entity: T): boolean;
}

// Concrete specifications
class OverdueOrderSpec implements Specification<Order> {
  constructor(private readonly asOf: Date) {}

  isSatisfiedBy(order: Order): boolean {
    return (
      order.status.isPending() &&
      order.createdAt < new Date(this.asOf.getTime() - 7 * 24 * 60 * 60 * 1000)
    );
  }
}

class HighValueOrderSpec implements Specification<Order> {
  constructor(private readonly threshold: Money) {}

  isSatisfiedBy(order: Order): boolean {
    return order.total.isGreaterThan(this.threshold);
  }
}

// Repository supports specifications
interface OrderRepository {
  findMatching(spec: Specification<Order>): Promise<Order[]>;
}

// Usage
const overdueHighValue = new AndSpecification(
  new OverdueOrderSpec(new Date()),
  new HighValueOrderSpec(Money.of(1000, "USD"))
);
const orders = await orderRepo.findMatching(overdueHighValue);
```

**Rule:** Use specifications when query criteria are complex, reusable, or need to be testable in isolation. For simple queries, named repository methods (`findPendingOrders()`) are sufficient.

---

## Domain Events

Domain events capture something that happened in the domain. They enable loose coupling between aggregates and bounded contexts.

### Pattern 14: Raising Events

Aggregates collect events during their operations. Events are published after the aggregate is persisted, not during mutation.

❌ **Bad: Publishing events during entity mutation**

```typescript
class Order {
  constructor(private eventBus: EventBus) {}  // Domain depends on infrastructure

  submit(): void {
    this._status = OrderStatus.Submitted;
    // What if save fails? Event already published!
    this.eventBus.publish(new OrderSubmittedEvent(this.id));
  }
}
```

✅ **Good: Collecting events, publishing after persistence**

```typescript
class Order {
  private _domainEvents: DomainEvent[] = [];

  submit(): void {
    if (this._items.length === 0) throw new EmptyOrderError(this.id);
    this._status = OrderStatus.Submitted;
    this._submittedAt = new Date();

    // Event collected — not yet published
    this._domainEvents.push(
      new OrderSubmittedEvent(this.id, this._customerId, this.total)
    );
  }

  get domainEvents(): ReadonlyArray<DomainEvent> {
    return [...this._domainEvents];
  }

  clearEvents(): void {
    this._domainEvents = [];
  }
}

// Repository publishes events after successful save
class OrderRepositoryImpl implements OrderRepository {
  constructor(
    private prisma: PrismaClient,
    private eventBus: EventBus
  ) {}

  async save(order: Order): Promise<void> {
    await this.prisma.order.upsert(/* ... */);

    // Events published only after successful persistence
    for (const event of order.domainEvents) {
      await this.eventBus.publish(event);
    }
    order.clearEvents();
  }
}
```

**Rule:** Aggregates collect events. Events are published after the aggregate is persisted. This guarantees no events for failed operations.

---

### Pattern 15: Event Structure

Domain events are named in past tense — they describe something that already happened. They carry enough data for handlers to react without querying back.

❌ **Bad: Vague event with minimal data**

```typescript
class OrderEvent {
  type: string;       // "order_change" — what changed?
  orderId: string;
  timestamp: Date;
  // Handler must query the database to understand what happened
}
```

✅ **Good: Specific, self-describing event**

```typescript
abstract class DomainEvent {
  public readonly occurredAt: Date = new Date();
  abstract readonly eventType: string;
}

class OrderSubmittedEvent extends DomainEvent {
  readonly eventType = "order.submitted";

  constructor(
    public readonly orderId: OrderId,
    public readonly customerId: CustomerId,
    public readonly totalAmount: Money,
    public readonly itemCount: number
  ) {
    super();
  }
}

class OrderCancelledEvent extends DomainEvent {
  readonly eventType = "order.cancelled";

  constructor(
    public readonly orderId: OrderId,
    public readonly customerId: CustomerId,
    public readonly reason: string
  ) {
    super();
  }
}

class PaymentReceivedEvent extends DomainEvent {
  readonly eventType = "payment.received";

  constructor(
    public readonly paymentId: PaymentId,
    public readonly orderId: OrderId,
    public readonly amount: Money,
    public readonly method: string
  ) {
    super();
  }
}
```

**Rule:** Event names are past tense (`OrderSubmitted`, not `SubmitOrder`). Events carry relevant IDs and key data. Handlers should not need to query the database to understand the event.

---

### Pattern 16: Event Handlers

Event handlers react to domain events. They run after the originating transaction completes, enabling eventual consistency between aggregates.

❌ **Bad: Synchronous handler doing writes in same transaction**

```typescript
class OrderSubmittedHandler {
  handle(event: OrderSubmittedEvent): void {
    // Runs synchronously in the same transaction as order save
    // If this fails, the order save is rolled back — tight coupling
    const invoice = Invoice.createFrom(event.orderId, event.totalAmount);
    this.invoiceRepo.save(invoice);  // Same transaction
    this.emailService.send(event.customerId, "Order confirmed");  // Blocking
  }
}
```

✅ **Good: Asynchronous handlers with eventual consistency**

```typescript
class SendOrderConfirmationHandler {
  constructor(private emailNotifier: EmailNotifier) {}

  async handle(event: OrderSubmittedEvent): Promise<void> {
    await this.emailNotifier.sendOrderConfirmation(
      event.customerId,
      event.orderId,
      event.totalAmount
    );
  }
}

class CreateInvoiceHandler {
  constructor(private invoiceRepo: InvoiceRepository) {}

  async handle(event: OrderSubmittedEvent): Promise<void> {
    const invoice = Invoice.createFor(event.orderId, event.totalAmount);
    await this.invoiceRepo.save(invoice);
  }
}

class UpdateInventoryHandler {
  constructor(private inventoryService: InventoryService) {}

  async handle(event: OrderSubmittedEvent): Promise<void> {
    await this.inventoryService.reserveStock(event.orderId);
  }
}

// Event dispatcher — wires events to handlers
class EventDispatcher {
  private handlers = new Map<string, DomainEventHandler[]>();

  register(eventType: string, handler: DomainEventHandler): void {
    const existing = this.handlers.get(eventType) ?? [];
    existing.push(handler);
    this.handlers.set(eventType, existing);
  }

  async dispatch(event: DomainEvent): Promise<void> {
    const handlers = this.handlers.get(event.eventType) ?? [];
    await Promise.allSettled(
      handlers.map((h) => h.handle(event))
    );
  }
}
```

**Rule:** Event handlers run after the originating aggregate is persisted. Each handler is independent — one handler's failure should not prevent others from running. Use `Promise.allSettled` for parallel handler execution.

---

## Domain Services

Domain services contain business logic that doesn't belong to a single entity or value object. They coordinate operations across multiple aggregates.

### Pattern 17: When to Use Domain Services

Use a domain service when business logic spans multiple aggregates and cannot be placed in either one.

❌ **Bad: Forcing cross-aggregate logic into one entity**

```typescript
class Order {
  // Order shouldn't know about inventory or pricing rules
  submit(inventory: Inventory, pricingRules: PricingRules): void {
    for (const item of this._items) {
      if (!inventory.hasStock(item.productId, item.quantity)) {
        throw new OutOfStockError(item.productId);
      }
      const price = pricingRules.calculatePrice(item.productId, item.quantity);
      item.setPrice(price);
    }
    inventory.reserveAll(this._items);  // Order modifying inventory!
    this._status = OrderStatus.Submitted;
  }
}
```

✅ **Good: Domain service coordinating multiple aggregates**

```typescript
class OrderSubmissionService {
  constructor(
    private inventoryService: InventoryAvailabilityService,
    private pricingService: PricingService
  ) {}

  async submit(order: Order): Promise<void> {
    // Check inventory (reads from Inventory aggregate)
    for (const item of order.items) {
      const available = await this.inventoryService.checkAvailability(
        item.productId,
        item.quantity
      );
      if (!available) {
        throw new InsufficientStockError(item.productId);
      }
    }

    // Calculate final prices (might involve discount rules across items)
    const finalTotal = await this.pricingService.calculateTotal(order.items);

    // Submit the order (entity handles its own state transition)
    order.submit(finalTotal);
  }
}
```

**Rule:** Domain services coordinate. They don't contain state. If logic involves a single aggregate, put it in the aggregate. If it spans aggregates, put it in a domain service.

---

### Pattern 18: Domain Service vs Application Service

Domain services contain business rules. Application services orchestrate use cases — they handle transactions, authorization, and coordination.

❌ **Bad: Mixing orchestration with business rules**

```typescript
class OrderApplicationService {
  async submitOrder(userId: string, orderId: string): Promise<void> {
    // Authorization (application concern)
    const user = await this.userRepo.findById(userId);
    if (!user) throw new UnauthorizedError();

    // Business rule (domain concern — should not be here)
    const order = await this.orderRepo.findById(orderId);
    if (order.total.isGreaterThan(Money.of(10000, "USD"))) {
      order.requireApproval();
    }

    // More business logic mixed with orchestration
    const inventory = await this.inventoryRepo.findByProducts(order.productIds);
    for (const item of order.items) {
      if (inventory.getStock(item.productId) < item.quantity) {
        throw new Error("Out of stock");
      }
    }

    // Transaction (application concern)
    await this.db.transaction(async () => {
      await this.orderRepo.save(order);
      await this.inventoryRepo.reserveAll(order.items);
    });
  }
}
```

✅ **Good: Clear separation**

```typescript
// Domain service — pure business logic
class OrderSubmissionService {
  async validateAndSubmit(order: Order, inventory: Inventory): void {
    // Business rules only — no database, no auth, no transactions
    inventory.ensureAvailability(order.items);
    order.submit();
  }
}

// Application service — orchestration
class SubmitOrderUseCase {
  constructor(
    private orderRepo: OrderRepository,
    private inventoryRepo: InventoryRepository,
    private submissionService: OrderSubmissionService,
    private unitOfWork: UnitOfWork
  ) {}

  async execute(request: SubmitOrderRequest): Promise<void> {
    // Load aggregates
    const order = await this.orderRepo.findById(OrderId.from(request.orderId));
    if (!order) throw new OrderNotFoundError(request.orderId);

    const inventory = await this.inventoryRepo.findByProducts(order.productIds);

    // Delegate business logic to domain service
    this.submissionService.validateAndSubmit(order, inventory);

    // Persist changes in a transaction
    await this.unitOfWork.execute(async () => {
      await this.orderRepo.save(order);
      await this.inventoryRepo.save(inventory);
    });
  }
}
```

**Rule:** Domain services have business logic, no infrastructure concerns. Application services (use cases) orchestrate: load aggregates, call domain services, persist results, handle transactions.

---

## Anti-Patterns

### Anemic Domain Model

❌ **Bad: Entities are just data bags, all logic in services**

```typescript
class Account {
  balance: number;
  status: string;
  overdraftLimit: number;
}

class AccountService {
  withdraw(account: Account, amount: number): void {
    if (account.status !== "active") throw new Error("Inactive");
    if (account.balance - amount < -account.overdraftLimit) throw new Error("Limit");
    account.balance -= amount;
  }
}
```

✅ **Good: Entity encapsulates its own rules**

```typescript
class Account {
  withdraw(amount: Money): void {
    if (!this._status.isActive()) throw new InactiveAccountError(this.id);
    if (this.wouldExceedOverdraftLimit(amount)) throw new OverdraftLimitError(this.id);
    this._balance = this._balance.subtract(amount);
  }

  private wouldExceedOverdraftLimit(amount: Money): boolean {
    const resultingBalance = this._balance.subtract(amount);
    return resultingBalance.amount < -this._overdraftLimit.amount;
  }
}
```

### Aggregate of Everything

Keep aggregates small. If loading an aggregate requires loading hundreds of related objects, the boundary is too wide. Split into smaller aggregates connected by ID references.

### Repository Overuse

Don't create repositories for value objects or child entities. Only aggregate roots get repositories. OrderItem is saved through OrderRepository, not through its own repository.

---

## Quick Reference

| Pattern | What | Key Rule |
|---------|------|----------|
| Value Object | Immutable, no identity, validated on creation | Equality by value; operations return new instances |
| Entity | Has identity, mutable state | Equality by ID; encapsulates behavior and invariants |
| Aggregate | Consistency boundary around root + children | All changes through root; reference other aggregates by ID |
| Aggregate Root | Entry point to the aggregate | External code only calls root methods |
| Repository | Persistence interface for aggregates | One per aggregate root; returns domain objects, not DB rows |
| Specification | Encapsulated query criteria | Use for complex, reusable, testable query logic |
| Domain Event | Past-tense record of something that happened | Collected during mutation, published after persistence |
| Event Handler | Reacts to domain events | Runs after originating transaction; handlers are independent |
| Domain Service | Cross-aggregate business logic | No state; coordinates entities; no infrastructure concerns |
| Application Service | Use case orchestration | Loads aggregates, calls domain services, manages transactions |


path: references/clean-architecture-guide.md
content: # Clean Architecture Guide

Layer-by-layer guide to implementing Clean Architecture with TypeScript. Each layer has clear responsibilities, and dependencies always point inward.

---

## Core Concept

Clean Architecture organizes code into concentric layers. The innermost layer (Entities) has zero dependencies. Each outer layer depends only on layers inside it. This is the **Dependency Rule**: source code dependencies always point inward.

```
┌─────────────────────────────────────────────────┐
│  Infrastructure (DB, HTTP, Config)              │
│  ┌─────────────────────────────────────────┐    │
│  │  Adapters (Controllers, Repos, Mappers) │    │
│  │  ┌─────────────────────────────────┐    │    │
│  │  │  Use Cases (Application Logic)  │    │    │
│  │  │  ┌─────────────────────────┐    │    │    │
│  │  │  │  Entities (Domain)      │    │    │    │
│  │  │  └─────────────────────────┘    │    │    │
│  │  └─────────────────────────────────┘    │    │
│  └─────────────────────────────────────────┘    │
└─────────────────────────────────────────────────┘
```

Inner layers define **interfaces**. Outer layers **implement** them.

---

## Layer 1: Entities (Domain Layer)

The innermost layer contains business objects and rules that exist independent of any application. These are the core concepts of your domain.

### Pattern 1: Rich Entity

Entities encapsulate both data and behavior. An entity with only data and no methods is an "anemic model" — an anti-pattern.

❌ **Bad: Anemic entity — just a data bag**

```typescript
// Entity has no behavior — all logic lives in external services
class User {
  id: string;
  email: string;
  name: string;
  status: "active" | "suspended" | "deactivated";
  failedLoginAttempts: number;
  lastLoginAt: Date | null;
}

// Service does all the work — entity is just a struct
class UserService {
  suspend(user: User): void {
    user.status = "suspended"; // Direct mutation from outside
  }

  canLogin(user: User): boolean {
    return user.status === "active" && user.failedLoginAttempts < 5;
  }
}
```

✅ **Good: Rich entity with encapsulated behavior**

```typescript
class User {
  private constructor(
    public readonly id: UserId,
    private _email: Email,
    private _name: string,
    private _status: "active" | "suspended" | "deactivated",
    private _failedLoginAttempts: number,
    private _lastLoginAt: Date | null
  ) {}

  static create(props: { email: Email; name: string }): User {
    return new User(
      UserId.generate(),
      props.email,
      props.name,
      "active",
      0,
      null
    );
  }

  get email(): Email { return this._email; }
  get status(): string { return this._status; }

  canLogin(): boolean {
    return this._status === "active" && this._failedLoginAttempts < 5;
  }

  recordFailedLogin(): void {
    this._failedLoginAttempts++;
    if (this._failedLoginAttempts >= 5) {
      this._status = "suspended";
    }
  }

  recordSuccessfulLogin(): void {
    this._failedLoginAttempts = 0;
    this._lastLoginAt = new Date();
  }

  deactivate(): void {
    if (this._status === "deactivated") {
      throw new Error("User already deactivated");
    }
    this._status = "deactivated";
  }
}
```

**Rule:** Push behavior into entities. If a service is just getting data from an entity, deciding something, and setting data back — that logic belongs in the entity.

---

### Pattern 2: Value Objects in Entities

Value objects replace primitive types with domain-specific types that carry validation and behavior.

❌ **Bad: Primitive obsession — email as raw string**

```typescript
class User {
  email: string;

  constructor(email: string) {
    // Validation scattered across codebase
    this.email = email;
  }
}

// Validation repeated everywhere email is used
function sendEmail(email: string): void {
  if (!email.includes("@")) throw new Error("Invalid email");
  // ...
}
```

✅ **Good: Email value object with built-in validation**

```typescript
class Email {
  private readonly value: string;

  private constructor(value: string) {
    this.value = value;
  }

  static create(value: string): Email {
    if (!value || !value.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
      throw new InvalidEmailError(value);
    }
    return new Email(value.toLowerCase().trim());
  }

  equals(other: Email): boolean {
    return this.value === other.value;
  }

  get domain(): string {
    return this.value.split("@")[1];
  }

  toString(): string {
    return this.value;
  }
}

// Usage in entity — impossible to have an invalid email
class User {
  constructor(
    public readonly id: UserId,
    private _email: Email  // Always valid by construction
  ) {}
}
```

**Rule:** If a primitive has validation rules, formatting behavior, or is compared for equality — wrap it in a value object. Validation happens once at creation, not everywhere the value is used.

---

### Pattern 3: Entity Identity

Entities are compared by identity (ID), not by their attributes. Two users with the same name are different users. Value objects are the opposite — compared by their content.

❌ **Bad: No clear identity semantics**

```typescript
class Product {
  name: string;
  price: number;

  equals(other: Product): boolean {
    // Comparing by attributes — is this an entity or value object?
    return this.name === other.name && this.price === other.price;
  }
}
```

✅ **Good: Entity equality by ID, value object equality by content**

```typescript
// Entity: compared by ID
class Product {
  constructor(
    public readonly id: ProductId,
    private _name: string,
    private _price: Money
  ) {}

  equals(other: Product): boolean {
    return this.id.equals(other.id);
  }
}

// Value Object: compared by content
class Money {
  constructor(
    public readonly amount: number,
    public readonly currency: string
  ) {}

  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency;
  }

  add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new CurrencyMismatchError(this.currency, other.currency);
    }
    return new Money(this.amount + other.amount, this.currency);
  }
}
```

**Rule:** Entities have identity (compared by ID). Value objects have no identity (compared by attributes). This distinction drives how you implement equality and persistence.

---

## Layer 2: Use Cases (Application Layer)

Use cases contain application-specific business rules. They orchestrate entities and define the application's behavior. Each use case represents a single operation the system can perform.

### Pattern 4: Use Case Structure

Each use case is a single class with an `execute` method. It takes a request DTO and returns a response DTO.

❌ **Bad: Business logic in the controller**

```typescript
class UserController {
  async createUser(req: Request, res: Response): Promise<void> {
    // Business logic mixed with HTTP handling
    const existing = await this.userRepo.findByEmail(req.body.email);
    if (existing) {
      res.status(409).json({ error: "Email taken" });
      return;
    }

    const hashedPassword = await bcrypt.hash(req.body.password, 12);
    const user = { id: uuid(), email: req.body.email, [REDACTED_SECRET] };
    await this.userRepo.save(user);
    await this.emailService.sendWelcome(user.email);

    res.status(201).json({ id: user.id, email: user.email });
  }
}
```

✅ **Good: Dedicated use case class**

```typescript
// Request and response DTOs — plain data, no framework types
interface CreateUserRequest {
  email: string;
  name: string;
  [REDACTED_SECRET];
}

interface CreateUserResponse {
  id: string;
  email: string;
  name: string;
}

class CreateUserUseCase {
  constructor(
    private readonly userRepository: UserRepository,
    private readonly passwordHasher: PasswordHasher,
    private readonly emailNotifier: EmailNotifier
  ) {}

  async execute(request: CreateUserRequest): Promise<CreateUserResponse> {
    const email = Email.create(request.email);

    const existing = await this.userRepository.findByEmail(email);
    if (existing) {
      throw new UserAlreadyExistsError(email);
    }

    const hashedPassword = await this.passwordHasher.hash(request.password);
    const user = User.create({ email, name: request.name, hashedPassword });

    await this.userRepository.save(user);
    await this.emailNotifier.sendWelcome(email);

    return { id: user.id.toString(), email: email.toString(), name: request.name };
  }
}
```

**Rule:** One use case = one class = one `execute` method. Use cases know nothing about HTTP, CLI, or any delivery mechanism. They take plain DTOs and return plain DTOs.

---

### Pattern 5: Use Case Composition

When one operation needs logic from another, resist the urge to call one use case from another. Instead, extract shared logic into a domain service.

❌ **Bad: Use case calling another use case**

```typescript
class CreateOrderUseCase {
  constructor(private createInvoiceUseCase: CreateInvoiceUseCase) {}

  async execute(request: CreateOrderRequest): Promise<CreateOrderResponse> {
    const order = Order.create(request);
    await this.orderRepo.save(order);

    // Tightly couples two use cases — what if invoice creation fails?
    // What if CreateInvoiceUseCase needs request data we don't have?
    await this.createInvoiceUseCase.execute({ orderId: order.id });

    return { orderId: order.id };
  }
}
```

✅ **Good: Shared domain service or domain events**

```typescript
// Option 1: Domain service for shared logic
class PricingService {
  calculateOrderTotal(items: OrderItem[], discounts: Discount[]): Money {
    // Shared pricing logic used by both orders and invoices
  }
}

// Option 2: Domain events for loose coupling
class CreateOrderUseCase {
  constructor(
    private orderRepo: OrderRepository,
    private eventBus: EventBus
  ) {}

  async execute(request: CreateOrderRequest): Promise<CreateOrderResponse> {
    const order = Order.create(request);
    await this.orderRepo.save(order);

    // Publish event — invoice creation is a separate concern
    await this.eventBus.publish(new OrderCreatedEvent(order.id));

    return { orderId: order.id.toString() };
  }
}
```

**Rule:** Use cases should not call other use cases. Extract shared logic into domain services, or decouple operations with domain events.

---

### Pattern 6: Input/Output Boundaries

Use cases must not depend on delivery-mechanism types. Express Request objects, GraphQL contexts, or CLI arguments must never reach the use case layer.

❌ **Bad: Framework types leaking into use case**

```typescript
import { Request } from "express";

class GetUserUseCase {
  async execute(req: Request): Promise<any> {
    const userId = req.params.id;           // Express dependency
    const authToken = req.headers.authorization; // HTTP concept
    // ...
  }
}
```

✅ **Good: Plain DTOs at the boundary**

```typescript
// Input DTO — no framework types
interface GetUserRequest {
  userId: string;
  requesterId: string;  // Already extracted from auth token by controller
}

// Output DTO — no entity types
interface GetUserResponse {
  id: string;
  email: string;
  name: string;
  memberSince: string;
}

class GetUserUseCase {
  constructor(private userRepo: UserRepository) {}

  async execute(request: GetUserRequest): Promise<GetUserResponse> {
    const user = await this.userRepo.findById(UserId.from(request.userId));
    if (!user) throw new UserNotFoundError(request.userId);

    return {
      id: user.id.toString(),
      email: user.email.toString(),
      name: user.name,
      memberSince: user.createdAt.toISOString(),
    };
  }
}
```

**Rule:** The use case layer defines its own input/output types. Controllers map from framework types to these DTOs. Use cases never import from framework packages.

---

## Layer 3: Interface Adapters

Adapters convert data between the use case layer and the external world. Controllers translate HTTP requests into use case inputs. Repositories translate between domain objects and database records.

### Pattern 7: Controllers

Controllers are thin — they translate HTTP requests into use case calls and use case responses into HTTP responses.

❌ **Bad: Fat controller with business logic**

```typescript
class UserController {
  async updateProfile(req: Request, res: Response): Promise<void> {
    const user = await this.userRepo.findById(req.params.id);
    if (!user) { res.status(404).json({ error: "Not found" }); return; }

    // Business rules in the controller
    if (req.body.email && req.body.email !== user.email) {
      const existing = await this.userRepo.findByEmail(req.body.email);
      if (existing) { res.status(409).json({ error: "Email taken" }); return; }
      if (!this.isValidEmail(req.body.email)) {
        res.status(400).json({ error: "Invalid email" }); return;
      }
    }

    user.name = req.body.name || user.name;
    user.email = req.body.email || user.email;
    await this.userRepo.save(user);
    res.status(200).json(user);
  }
}
```

✅ **Good: Thin controller delegating to use case**

```typescript
class UserController {
  constructor(private updateProfileUseCase: UpdateProfileUseCase) {}

  async updateProfile(req: Request, res: Response): Promise<void> {
    try {
      const result = await this.updateProfileUseCase.execute({
        userId: req.params.id,
        name: req.body.name,
        email: req.body.email,
      });
      res.status(200).json(result);
    } catch (error) {
      if (error instanceof UserNotFoundError) {
        res.status(404).json({ error: error.message });
      } else if (error instanceof EmailAlreadyTakenError) {
        res.status(409).json({ error: error.message });
      } else {
        throw error;  // Let error middleware handle unexpected errors
      }
    }
  }
}
```

**Rule:** Controllers do three things: (1) extract data from the request, (2) call the use case, (3) map the result or error to an HTTP response. No business logic.

---

### Pattern 8: Presenters / Response Mapping

Entities should never be serialized directly to API responses. A presenter (or response mapper) converts domain objects to the shape the client expects.

❌ **Bad: Entity serialized directly**

```typescript
// Leaks internal state, ORM metadata, and implementation details
app.get("/users/:id", async (req, res) => {
  const user = await userRepo.findById(req.params.id);
  res.json(user); // Exposes password hash, internal IDs, etc.
});
```

✅ **Good: Presenter maps to response shape**

```typescript
class UserPresenter {
  static toResponse(user: User): UserResponse {
    return {
      id: user.id.toString(),
      email: user.email.toString(),
      displayName: user.name,
      memberSince: user.createdAt.toISOString(),
      // Omits: passwordHash, failedLoginAttempts, internal flags
    };
  }

  static toListResponse(users: User[]): UserListResponse {
    return {
      users: users.map(UserPresenter.toResponse),
      count: users.length,
    };
  }
}
```

**Rule:** Never expose domain entities to the outside world. Use presenters or response mappers to control exactly what data leaves the system.

---

### Pattern 9: Repository Implementations

The domain layer defines repository interfaces. The adapter layer implements them using specific technologies (Prisma, TypeORM, Knex, etc.).

❌ **Bad: ORM entities used as domain entities**

```typescript
// Domain layer directly depends on Prisma — can't test without database
import { PrismaClient } from "@prisma/client";

class UserService {
  constructor(private prisma: PrismaClient) {}

  async getUser(id: string) {
    // Prisma model leaked into domain — tightly coupled to ORM
    return this.prisma.user.findUnique({ where: { id } });
  }
}
```

✅ **Good: Repository maps between ORM models and domain entities**

```typescript
// Domain layer — defines what it needs
interface UserRepository {
  findById(id: UserId): Promise<User | null>;
  findByEmail(email: Email): Promise<User | null>;
  save(user: User): Promise<void>;
}

// Adapter layer — implements using Prisma
class PrismaUserRepository implements UserRepository {
  constructor(private prisma: PrismaClient) {}

  async findById(id: UserId): Promise<User | null> {
    const record = await this.prisma.user.findUnique({
      where: { id: id.toString() },
    });
    return record ? this.toDomain(record) : null;
  }

  async save(user: User): Promise<void> {
    const data = this.toPersistence(user);
    await this.prisma.user.upsert({
      where: { id: data.id },
      create: data,
      update: data,
    });
  }

  // Maps from DB record to domain entity
  private toDomain(record: PrismaUser): User {
    return User.reconstitute({
      id: UserId.from(record.id),
      email: Email.create(record.email),
      name: record.name,
      status: record.status,
    });
  }

  // Maps from domain entity to DB record
  private toPersistence(user: User): PrismaUserData {
    return {
      id: user.id.toString(),
      email: user.email.toString(),
      name: user.name,
      status: user.status,
    };
  }
}
```

**Rule:** Repository interfaces live in the domain layer. Repository implementations live in the adapter layer. The mapping between ORM models and domain entities happens inside the repository.

---

## Layer 4: Infrastructure

The outermost layer contains framework configuration, database connections, external service clients, and the application's composition root.

### Pattern 10: Dependency Injection via Composition Root

All wiring happens in a single place — the composition root. Use cases receive their dependencies through constructor injection.

❌ **Bad: Use case creates its own dependencies**

```typescript
class CreateOrderUseCase {
  async execute(request: CreateOrderRequest): Promise<void> {
    // Creating dependencies inside the use case
    const repo = new PrismaOrderRepository(new PrismaClient());
    const emailer = new SendGridEmailService(process.env.SENDGRID_KEY!);

    // Impossible to test without real database and SendGrid
    const order = Order.create(request);
    await repo.save(order);
    await emailer.sendConfirmation(order);
  }
}
```

✅ **Good: Dependencies injected through constructor**

```typescript
// Use case receives interfaces, not implementations
class CreateOrderUseCase {
  constructor(
    private readonly orderRepo: OrderRepository,
    private readonly emailNotifier: EmailNotifier
  ) {}

  async execute(request: CreateOrderRequest): Promise<void> {
    const order = Order.create(request);
    await this.orderRepo.save(order);
    await this.emailNotifier.sendConfirmation(order);
  }
}

// Composition root — wires everything together
function createApp(): Application {
  const prisma = new PrismaClient();
  const sendgrid = new SendGridClient(config.sendgridKey);

  // Repositories
  const userRepo = new PrismaUserRepository(prisma);
  const orderRepo = new PrismaOrderRepository(prisma);

  // External services
  const emailNotifier = new SendGridEmailNotifier(sendgrid);
  const paymentGateway = new StripePaymentGateway(config.stripeKey);

  // Use cases
  const createOrder = new CreateOrderUseCase(orderRepo, emailNotifier);
  const createUser = new CreateUserUseCase(userRepo, emailNotifier);

  // Controllers
  const userController = new UserController(createUser);
  const orderController = new OrderController(createOrder);

  return new Application(userController, orderController);
}
```

**Rule:** Dependencies flow inward at the type level (use cases depend on interfaces) and are wired outward at the composition root. The composition root is the only place that knows about all implementations.

---

## Testing Strategy by Layer

Each layer has a different testing approach. Inner layers are easier to test because they have fewer dependencies.

### Entities: Pure unit tests, no mocking needed

```typescript
describe("User", () => {
  it("should suspend after 5 failed login attempts", () => {
    const user = User.create({ email: Email.create("[REDACTED_SECRET]"), name: "Test" });

    for (let i = 0; i < 5; i++) {
      user.recordFailedLogin();
    }

    expect(user.status).toBe("suspended");
    expect(user.canLogin()).toBe(false);
  });
});
```

### Use Cases: Mock repository interfaces

```typescript
describe("CreateUserUseCase", () => {
  it("should reject duplicate emails", async () => {
    const userRepo: UserRepository = {
      findByEmail: vi.fn().mockResolvedValue(existingUser),
      save: vi.fn(),
      findById: vi.fn(),
    };

    const useCase = new CreateUserUseCase(userRepo, mockHasher, mockNotifier);

    await expect(
      useCase.execute({ email: "[REDACTED_SECRET]", name: "Test", password: "pw" })
    ).rejects.toThrow(UserAlreadyExistsError);

    expect(userRepo.save).not.toHaveBeenCalled();
  });
});
```

### Adapters: Integration tests with real implementations

```typescript
describe("PrismaUserRepository", () => {
  let repo: PrismaUserRepository;

  beforeAll(async () => {
    repo = new PrismaUserRepository(testPrismaClient);
  });

  it("should save and retrieve a user", async () => {
    const user = User.create({ email: Email.create("[REDACTED_SECRET]"), name: "Test" });
    await repo.save(user);

    const found = await repo.findById(user.id);
    expect(found).not.toBeNull();
    expect(found!.email.equals(user.email)).toBe(true);
  });
});
```

---

## Dependency Rule Violations

### Violation 1: Entity importing from framework

❌ **Bad:**

```typescript
import { Column, Entity, PrimaryColumn } from "typeorm"; // Framework import!

@Entity()
class User {
  @PrimaryColumn() id: string;
  @Column() email: string;
}
```

✅ **Good:**

```typescript
// Domain entity — no decorators, no framework imports
class User {
  constructor(
    public readonly id: UserId,
    private _email: Email
  ) {}
}

// ORM entity is a separate class in the adapter layer
```

### Violation 2: Use case returning HTTP status codes

❌ **Bad:**

```typescript
class GetUserUseCase {
  async execute(id: string): Promise<{ status: 200 | 404; body: any }> {
    const user = await this.repo.findById(id);
    if (!user) return { status: 404, body: { error: "Not found" } };
    return { status: 200, body: user };
  }
}
```

✅ **Good:**

```typescript
class GetUserUseCase {
  async execute(request: GetUserRequest): Promise<GetUserResponse> {
    const user = await this.repo.findById(UserId.from(request.userId));
    if (!user) throw new UserNotFoundError(request.userId);
    return { id: user.id.toString(), email: user.email.toString(), name: user.name };
  }
}
// Controller catches UserNotFoundError and returns 404
```

### Violation 3: Circular dependencies between layers

**Detection:** If module A imports from module B, and module B imports from module A — you have a circular dependency. This usually means a layer boundary is being crossed incorrectly.

**Fix:** Extract the shared type into an inner layer that both can depend on, or use dependency inversion (define an interface in the inner layer, implement it in the outer layer).

---

## Quick Reference

| Layer | Contains | Depends On | Tested With |
|-------|----------|------------|-------------|
| Entities | Business objects, value objects, domain errors | Nothing | Pure unit tests — no mocking |
| Use Cases | Application logic, DTOs, use case interfaces | Entities | Mocked repository/service interfaces |
| Adapters | Controllers, repositories, presenters, mappers | Use Cases, Entities | Integration tests with real implementations |
| Infrastructure | DB config, framework setup, composition root | Everything | E2E tests, smoke tests |


Security scan verdicts — ClawScan: clean | static analysis: suspicious | VirusTotal: clean | SkillSpector: clean (LOW)