All skills
ccheney avatar

/clean-ddd-hexagonal

@f71635f

Design or review backend domain and dependency boundaries using DDD, Clean Architecture, and ports/adapters. Use for aggregate modeling, bounded contexts, use-case isolation, or architecture refactoring; not routine CRUD changes.

Use this Skill: https://skilld.dev/gh/ccheney/robust-skills/clean-ddd-hexagonal

This session only. Nothing lands on disk.

referencesCHEATSHEET.md

≈3.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Quick Reference Cheatsheet

See SKILL.md for primary foundations and topic-specific references.

This cheatsheet summarizes an opinionated synthesis, not a single canonical architecture. Use DDD, Hexagonal, Clean Architecture, Onion Architecture, CQRS, and Event Sourcing independently when only one pattern fits the problem.

Contents

Layer Summary

flowchart TB
    subgraph Infra["INFRASTRUCTURE (Adapters)"]
        I1["REST/gRPC controllers"]
        I2["CLI handlers"]
        I3["Framework code"]
        I4["Database repositories"]
        I5["Message publishers"]
        I6["External service clients"]
    end

    subgraph App["APPLICATION (Use Cases)"]
        A1["Command/Query handlers"]
        A2["DTOs"]
        A3["Transaction management"]
        A4["Port interfaces"]
        A5["Application services"]
        A6["Event dispatching"]
    end

    subgraph Domain["DOMAIN (Business Logic)"]
        D1["Entities"]
        D2["Aggregates"]
        D3["Repository interfaces"]
        D4["Business rules"]
        D5["Value Objects"]
        D6["Domain Events"]
        D7["Domain Services"]
        D8["Specifications"]
    end

    Infra -->|depends on| App
    App -->|depends on| Domain

    style Infra fill:#6366f1,stroke:#4f46e5,color:white
    style App fill:#3b82f6,stroke:#2563eb,color:white
    style Domain fill:#10b981,stroke:#059669,color:white

Dependencies point inward


Pattern Boundaries

Pattern Use For Avoid Assuming
DDD Ubiquitous language, bounded contexts, aggregates It requires a specific folder layout
Hexagonal Ports/adapters around an application core Every port must be a separate interface
Clean Architecture Inward dependency rule and use-case boundaries Every project needs four layers
Onion Architecture Domain-centered dependency inversion It is mandatory in addition to Clean/Hexagonal
CQRS Divergent read/write models in a bounded context It should be system-wide by default
Event Sourcing Audit trails, temporal queries, replayable workflows It is a normal CRUD persistence choice

Quick Decision Trees

"Where does this code go?"

Is it a business rule or constraint?
├── YES → Domain layer
└── NO ↓

Is it orchestrating a use case?
├── YES → Application layer
└── NO ↓

Is it dealing with external systems (DB, API, UI)?
├── YES → Infrastructure layer
└── NO → Reconsider; probably domain

"Entity or Value Object?"

Does it have a unique identity that persists?
├── YES → Entity
└── NO ↓

Is it defined entirely by its attributes?
├── YES → Value Object
└── NO → Probably an Entity

"Aggregate boundary?"

Must these objects change together atomically?
├── YES → Same aggregate
└── NO ↓

Can one exist without the other?
├── YES → Different aggregates (reference by ID)
└── NO → Probably same aggregate

"Domain Service or Entity method?"

Does it naturally belong to one entity?
├── YES → Entity method
└── NO ↓

Does it require multiple aggregates?
├── YES → Domain Service
└── NO ↓

Is it stateless business logic?
├── YES → Domain Service
└── NO → Reconsider placement

Common Patterns Quick Reference

Value Object Template

export class Money {
  private constructor(
    private readonly _amount: number,
    private readonly _currency: string,
  ) {}

  static create(amount: number, currency: string): Money {
    if (amount < 0) throw new Error('Negative');
    return new Money(amount, currency);
  }

  add(other: Money): Money {
    return Money.create(this._amount + other._amount, this._currency);
  }

  get amount(): number { return this._amount; }
  get currency(): string { return this._currency; }

  equals(other: Money): boolean {
    return this._amount === other._amount && this._currency === other._currency;
  }
}

Entity Template

export class OrderItem extends Entity<OrderItemId> {
  private _quantity: Quantity;

  private constructor(
    id: OrderItemId,
    private readonly _productId: ProductId,
    quantity: Quantity,
    private readonly _unitPrice: Money,
  ) {
    super(id);
    this._quantity = quantity;
  }

  static create(productId: ProductId, quantity: Quantity, unitPrice: Money): OrderItem {
    return new OrderItem(OrderItemId.generate(), productId, quantity, unitPrice);
  }

  increaseQuantity(amount: number): void {
    this._quantity = this._quantity.add(amount);
  }

  get productId(): ProductId { return this._productId; }
  get quantity(): Quantity { return this._quantity; }
  get subtotal(): Money { return this._unitPrice.multiply(this._quantity.value); }
}

Aggregate Root Template

export class Order extends AggregateRoot<OrderId> {
  private readonly _customerId: CustomerId;
  private _items: OrderItem[] = [];
  private _status: OrderStatus;

  private constructor(id: OrderId, customerId: CustomerId) {
    super(id);
    this._customerId = customerId;
    this._status = OrderStatus.Draft;
  }

  static create(customerId: CustomerId): Order {
    const order = new Order(OrderId.generate(), customerId);
    order.addDomainEvent(new OrderCreated(order.id, customerId));
    return order;
  }

  addItem(productId: ProductId, quantity: Quantity, price: Money): void {
    this.assertCanModify();
    this._items.push(OrderItem.create(productId, quantity, price));
  }

  confirm(): void {
    this.assertCanModify();
    if (this._items.length === 0) throw new EmptyOrderError();
    this._status = OrderStatus.Confirmed;
    this.addDomainEvent(new OrderConfirmed(this.id, this.total));
  }

  private assertCanModify(): void {
    if (this._status === OrderStatus.Cancelled) {
      throw new InvalidOrderStateError('Order is cancelled');
    }
  }

  get total(): Money {
    return this._items.reduce((sum, item) => sum.add(item.subtotal), Money.create(0, 'USD'));
  }
}

Repository Interface Template

export interface IOrderRepository {
  findById(id: OrderId): Promise<Order | null>;
  save(order: Order): Promise<void>;
  delete(order: Order): Promise<void>;
}

Use Case Handler Template

export class PlaceOrderHandler {
  constructor(
    private readonly orderRepo: IOrderRepository,
    private readonly productRepo: IProductRepository,
    private readonly eventPublisher: IEventPublisher,
  ) {}

  async execute(command: PlaceOrderCommand): Promise<OrderId> {
    const order = Order.create(CustomerId.from(command.customerId));

    for (const item of command.items) {
      const product = await this.productRepo.findById(item.productId);
      if (!product) throw new ProductNotFoundError(item.productId);
      order.addItem(product.id, Quantity.create(item.quantity), product.price);
    }

    await this.orderRepo.save(order);
    await this.eventPublisher.publishAll(order.domainEvents);

    return order.id;
  }
}

Port Naming Conventions

Repository port placement varies by school: DDD-centered code often keeps aggregate repositories in domain/{aggregate}/repository; stricter Hexagonal layouts often group them under application/ports/driven/. Pick one convention per codebase.

Type Pattern Examples
Driver Port I{Action}UseCase IPlaceOrderUseCase, IGetOrderUseCase
Driven Port I{Resource}Repository IOrderRepository, IProductRepository
Driven Port I{Action}Service IPaymentService, INotificationService
Driven Port I{Resource}Gateway IPaymentGateway, IShippingGateway

Common Anti-Patterns

Anti-Pattern Problem Solution
Anemic Domain Entities are just data bags Put behavior in entities
Repository per table One repo per DB table One repo per aggregate
Fat Use Cases Business logic in handlers Move to domain
Leaky Abstraction Domain depends on ORM Keep domain pure
God Aggregate One massive aggregate Split into smaller ones
Unexamined cross-aggregate TX Coupling and contention Check atomicity requirements; use events when eventual consistency fits
Direct Layer Skip Controller -> Repository in this architecture style Route through application use case
Premature CQRS Adding complexity early Start simple, evolve
Event Proliferation Too many fine-grained events May signal context boundary

Dependency Rules Matrix

Domain Application Infrastructure
Domain ✅ ❌ ❌
Application ✅ ✅ ❌
Infrastructure ✅ ✅ ✅

✅ = Can depend on ❌ = Cannot depend on


Hexagonal Quick Reference

flowchart LR
    subgraph Driver["DRIVER (Left/Primary/Inbound)"]
        direction TB
        D1["REST Controller"]
        D2["gRPC Service"]
        D3["CLI Command"]
        D4["Message Consumer"]
        DP["Port (Interface)"]
        D1 & D2 & D3 & D4 -->|calls| DP
    end

    subgraph App["Application"]
        Core[" "]
    end

    subgraph Driven["DRIVEN (Right/Secondary/Outbound)"]
        direction TB
        DRP["Port (Interface)"]
        DR1["Database Repository"]
        DR2["Message Publisher"]
        DR3["External API Client"]
        DR4["Cache Adapter"]
        DR1 & DR2 & DR3 & DR4 -->|implements| DRP
    end

    Driver -->|"How world\nuses app"| App
    App -->|"How app\nuses world"| Driven

    style Driver fill:#3b82f6,stroke:#2563eb,color:white
    style App fill:#10b981,stroke:#059669,color:white
    style Driven fill:#f59e0b,stroke:#d97706,color:white

When to Use / Skip

Use Clean + DDD + Hexagonal When:

  • ✅ Complex business domain with many rules
  • ✅ Long-lived system (years of maintenance)
  • ✅ Domain/dependency boundaries that help the team maintain the system
  • ✅ Need to swap infrastructure (DB, broker, etc.)
  • ✅ High test coverage required
  • ✅ Multiple entry points (API, CLI, events, scheduled jobs)

Skip When:

  • ❌ Simple CRUD application (most applications)
  • ❌ Prototype / MVP / throwaway code
  • ❌ Short-lived project
  • ❌ Trivial business logic

Complexity Ladder (Start Simple)

Level 1: Simple layered (Controller → Service → Repository)
   ↓ When business rules grow complex
Level 2: Domain model (Entities with behavior)
   ↓ When need multiple entry points
Level 3: Hexagonal (Ports & Adapters)
   ↓ When read/write patterns diverge significantly
Level 4: CQRS (Separate read/write models)
   ↓ When need complete audit trail / temporal queries
Level 5: Event Sourcing (Store events, derive state)

These are independent design options, not mandatory stages. Choose the patterns that address demonstrated requirements; team size alone is not a deciding factor.


File Naming Conventions

domain/
├── order/
│   ├── order.ts                    # Aggregate root
│   ├── order_item.ts               # Entity
│   ├── value_objects.ts            # OrderId, Money, etc.
│   ├── events.ts                   # OrderCreated, etc.
│   ├── repository.ts               # IOrderRepository
│   ├── services.ts                 # Domain services
│   └── errors.ts                   # OrderError, etc.

application/
├── place_order/
│   ├── command.ts                  # PlaceOrderCommand
│   ├── handler.ts                  # PlaceOrderHandler
│   └── port.ts                     # IPlaceOrderUseCase

infrastructure/
├── postgres/
│   ├── order_repository.ts         # PostgresOrderRepository
│   └── mappers/
│       └── order_mapper.ts         # Domain <-> DB mapping

Resources

Books & Primary Articles

  • Clean Architecture (Robert C. Martin, 2017)
  • Domain-Driven Design (Eric Evans, 2003)
  • Implementing Domain-Driven Design (Vaughn Vernon, 2013)
  • Onion Architecture (Jeffrey Palermo, 2008 article series)
  • Hexagonal Architecture Explained (Alistair Cockburn, 2024)
  • Get Your Hands Dirty on Clean Architecture (Tom Hombergs, 2019)

Supplemental Syntheses

  • Herberto Graça, Clean Architecture comparison and Explicit Architecture articles (opinionated synthesis, not canonical source)
  • Tom Hombergs, practical Clean Architecture examples

Reference Implementations

Official Documentation

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The clean-ddd-hexagonal skill provides comprehensive architectural guidelines and code templates for implementing Domain-Driven Design (DDD), Clean Architecture, and Hexagonal Architecture. It is a purely educational resource containing no malicious code or security threats.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    2/8 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at f71635f. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 weeks ago.

Activeupdated 3 weeks ago
  • Backend
  • ddd
  • hexagonal-architecture
  • clean-architecture
  • domain-driven-design
  • microservices
  • cqrs
  • event-sourcing
  • repository-pattern
  • aggregate

README badge

README badge for ccheney/robust-skills/clean-ddd-hexagonal

Applies DDD tactical patterns, Clean Architecture dependency rules, and Hexagonal ports/adapters to backend systems. Use when modeling complex business domains, designing microservices with multiple entry points, or building systems that need to swap infrastructure or maintain high test coverage over years. Language-agnostic across Go, Rust, Python, TypeScript, Java, and C#.

Generated from the current SKILL.md.

Does this skill apply to all backend languages?
Yes. The skill is language-agnostic and covers Go, Rust, Python, TypeScript, Java, and C#. The patterns (DDD, Hexagonal, Clean Architecture) are design principles, not language-specific frameworks.
When should I NOT use this architecture?
Skip it for simple CRUD systems, prototypes, MVPs, solo development, single entry points, or codebases unlikely to need infrastructure swaps. The skill explicitly recommends starting simple and only evolving complexity when justified by team size, domain complexity, or long-term maintenance needs.
Do I have to use CQRS and Event Sourcing?
No. The skill emphasizes that most systems do not need full CQRS or Event Sourcing. Use CQRS only when reads and writes have divergent workloads, and Event Sourcing only when you need audit trails or temporal queries.
What is the core rule this skill enforces?
Dependencies point inward only: Infrastructure depends on Application, which depends on Domain. Domain has zero external dependencies. This dependency rule is non-negotiable and the skill includes validation techniques to catch violations.
Does this skill provide code examples or templates?
The skill includes reference files covering layers, DDD tactical patterns, Hexagonal ports/adapters, CQRS/events, testing strategies, and a quick decision cheatsheet, but the main SKILL.md provides pseudocode and decision trees rather than full runnable examples.

Generated from the current SKILL.md. These answers refresh after source changes.