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.

referencesDDD-STRATEGIC.md

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

DDD Strategic Patterns

Sources:

Contents

Overview

Strategic DDD patterns help decompose large systems into manageable parts with clear boundaries. They answer: "How do we divide a complex domain?"

DDD is fundamentally collaborative. The patterns below emerge from conversations, whiteboarding, and modeling sessions with domain experts—not from coding alone.


Domain Discovery Techniques

EventStorming

A workshop technique (Alberto Brandolini) for discovering domain events, aggregates, and bounded contexts. Standard sticky-note colors:

Orange:        Domain Event (past tense: "OrderPlaced")
Blue:          Command (imperative: "Place Order")
Small yellow:  Actor (who issues the command)
Large yellow:  Aggregate (noun: "Order")
Pink:          External System (payment gateway, carrier)
Lilac:         Policy / reaction ("When PaymentFailed, notify customer")
Purple:        Hot Spot (problem, question, disagreement)
Green:         Read Model (information the actor decides from)

Colors vary slightly by workshop format; keep the legend visible and consistent within a session.

Workshop flow:

  1. Chaotic exploration — Everyone adds events they know about
  2. Timeline ordering — Arrange events chronologically
  3. Identify aggregates — Group related events
  4. Find boundaries — Where language changes = bounded context boundary
  5. Surface problems — Mark unclear areas for follow-up

Context Mapping Workshop

For existing systems, map how bounded contexts currently interact:

  1. List all systems/services
  2. Identify which team owns each
  3. Draw relationships (upstream/downstream)
  4. Label relationship types (ACL, Conformist, etc.)
  5. Identify pain points in current integrations

Ubiquitous Language

The foundation of DDD. A shared vocabulary between developers and domain experts that appears in:

  • Code (class names, method names)
  • Documentation
  • Conversations
  • UI labels

Principles

  1. One language per bounded context - Different contexts may use the same word differently
  2. Code reflects the language - Order.confirm() not Order.setStatus("confirmed")
  3. Evolve together - When language changes, code changes

Example

❌ Technical language:
   "Set the order entity's status field to 2 and insert a record"

✅ Ubiquitous language:
   "Confirm the order and record that it was confirmed"
// ❌ Technical, not ubiquitous
class Order {
  setStatus(status: number): void { this.status = status; }
}

// ✅ Ubiquitous language
class Order {
  confirm(): void {
    if (this.status !== OrderStatus.Pending) {
      throw new OrderCannotBeConfirmedException(this.id);
    }
    this.status = OrderStatus.Confirmed;
    this.confirmedAt = new Date();
    this.addDomainEvent(new OrderConfirmed(this.id));
  }
}

Bounded Contexts

A semantic boundary where a particular domain model applies. Within a bounded context, terms have precise, unambiguous meaning.

Key insight: Polysemy (same word, different meanings) across departments is natural, not a problem. On what drives context boundaries: "Usually the dominant one is human culture, since models act as Ubiquitous Language, you need a different model when the language changes." — Martin Fowler

Key Concepts

  • Each bounded context has its own ubiquitous language
  • Each bounded context has its own model
  • The same real-world concept may have different representations in different contexts

Example: E-Commerce System

flowchart TB
    subgraph ECommerce["E-Commerce System"]
        subgraph Sales["Sales Context"]
            SC1["Customer: id, email, preferences"]
            SC2["Order: items, total, status"]
        end
        subgraph Shipping["Shipping Context"]
            SH1["Recipient: name, address, phone"]
            SH2["Shipment: packages, carrier, trackingNo"]
        end
        subgraph Billing["Billing Context"]
            BC1["Payer: name, billingAddress, paymentMethod"]
            BC2["Invoice: lineItems, total, dueDate"]
        end
        subgraph Catalog["Catalog Context"]
            CC1["Product: name, description, price"]
            CC2["(no customer concept)"]
        end
    end

    style Sales fill:#3b82f6,stroke:#2563eb,color:white
    style Shipping fill:#10b981,stroke:#059669,color:white
    style Billing fill:#f59e0b,stroke:#d97706,color:white
    style Catalog fill:#8b5cf6,stroke:#7c3aed,color:white

"Customer" means different things:

  • Sales: Email, preferences, order history
  • Shipping: Delivery address, phone number
  • Billing: Payment methods, billing address

Bounded Context = Microservice Boundary

In microservices, each bounded context typically becomes a separate service:

flowchart LR
    subgraph Sales["Sales Service"]
        S1["Orders DB"]
        S2["Order API"]
    end
    subgraph Shipping["Shipping Service"]
        SH1["Shipments DB"]
        SH2["Shipping API"]
    end
    subgraph Billing["Billing Service"]
        B1["Invoices DB"]
        B2["Billing API"]
    end

    Sales -->|events| Shipping
    Shipping -->|events| Billing
    Sales -.->|Integration Events| Events[("Event Bus")]
    Shipping -.-> Events
    Billing -.-> Events

    style Sales fill:#3b82f6,stroke:#2563eb,color:white
    style Shipping fill:#10b981,stroke:#059669,color:white
    style Billing fill:#f59e0b,stroke:#d97706,color:white

Subdomains

Areas of business expertise. Subdomains are discovered, not designed.

Types

Type Description Investment Example
Core Competitive advantage High Product recommendation engine
Supporting Necessary but not unique Medium Order management
Generic Commodity, buy/outsource Low Email sending, payments

Identification Questions

  1. What makes us different from competitors? → Core
  2. What do we need but isn't our specialty? → Supporting
  3. What does everyone need the same way? → Generic

Example: E-Commerce

flowchart TB
    subgraph Subdomains["Subdomains"]
        subgraph Core["CORE"]
            C1["Product search & recommendations"]
            C2["Pricing engine"]
            C3["Personalization"]
        end
        subgraph Supporting["SUPPORTING"]
            S1["Order management"]
            S2["Inventory"]
            S3["Customer support"]
            S4["Reporting"]
        end
        subgraph Generic["GENERIC"]
            G1["Authentication (Auth0)"]
            G2["Payments (Stripe)"]
            G3["Email (SendGrid)"]
            G4["File storage (S3)"]
        end
    end

    Core --> CoreStrat["Build in-house\nBest developers"]
    Supporting --> SuppStrat["Build or buy\nSolid but simple"]
    Generic --> GenStrat["Use third-party\nDon't reinvent"]

    style Core fill:#ef4444,stroke:#dc2626,color:white
    style Supporting fill:#f59e0b,stroke:#d97706,color:white
    style Generic fill:#6b7280,stroke:#4b5563,color:white

Context Mapping

Describes relationships between bounded contexts.

Relationship Patterns

Partnership

Two contexts succeed or fail together. Teams coordinate closely.

flowchart LR
    A["Context A"] <-->|"Partnership\nJoint planning\nShared success"| B["Context B"]

    style A fill:#3b82f6,stroke:#2563eb,color:white
    style B fill:#3b82f6,stroke:#2563eb,color:white
Shared Kernel

Two contexts share a subset of the domain model.

flowchart LR
    subgraph A["Context A"]
        SK["Shared Kernel"]
    end
    subgraph B["Context B"]
        B1[" "]
    end

    SK <-->|shared| B

    style A fill:#3b82f6,stroke:#2563eb,color:white
    style B fill:#10b981,stroke:#059669,color:white
    style SK fill:#f59e0b,stroke:#d97706,color:white

Warning: Shared kernels create coupling. Use sparingly.

Customer-Supplier

Upstream context provides what downstream needs.

flowchart LR
    U["Upstream\n(Supplier)"] -->|"Provides API"| D["Downstream\n(Customer)"]

    style U fill:#3b82f6,stroke:#2563eb,color:white
    style D fill:#10b981,stroke:#059669,color:white
Conformist

Downstream conforms to upstream's model with no negotiation power.

flowchart LR
    U["Upstream\n(Dictator)"] -->|"Take it or leave it"| D["Downstream\n(Conformist)\nUses their model"]

    style U fill:#ef4444,stroke:#dc2626,color:white
    style D fill:#6b7280,stroke:#4b5563,color:white

Example: Integrating with a third-party API (Stripe, AWS).

Anti-Corruption Layer (ACL)

Translation layer protecting your model from external models.

flowchart LR
    Ext["External\nContext"] --> ACL["ACL\nTranslator + Adapter"]
    ACL --> Your["Your\nContext"]

    ACL -.->|"Translates external\nmodel to your model"| Note[" "]

    style Ext fill:#ef4444,stroke:#dc2626,color:white
    style ACL fill:#f59e0b,stroke:#d97706,color:white
    style Your fill:#10b981,stroke:#059669,color:white
    style Note fill:none,stroke:none

Use when:

  • Integrating with legacy systems
  • Integrating with third-party APIs
  • External model is messy or poorly designed
// Anti-Corruption Layer Example
// infrastructure/external/stripe/stripe_payment_acl.ts

import Stripe from 'stripe';
import { Payment, PaymentStatus } from '@/domain/payment/payment';
import { PaymentId } from '@/domain/payment/value_objects';
import { PaymentCompleted } from '@/domain/payment/events';
import { DomainEvent } from '@/domain/shared/domain_event';
import { Money } from '@/domain/shared/money';

export class StripePaymentACL {
  constructor(private readonly stripe: Stripe) {}

  async createPayment(payment: Payment): Promise<string> {
    const paymentIntent = await this.stripe.paymentIntents.create({
      amount: payment.amount.cents,
      currency: payment.amount.currency.toLowerCase(),
      metadata: {
        orderId: payment.orderId.value,
        customerId: payment.customerId.value,
      },
    });

    return paymentIntent.id;
  }

  translateStatus(stripeStatus: string): PaymentStatus {
    const mapping: Record<string, PaymentStatus> = {
      'requires_payment_method': PaymentStatus.Pending,
      'requires_confirmation': PaymentStatus.Pending,
      'requires_action': PaymentStatus.Pending,
      'processing': PaymentStatus.Processing,
      'succeeded': PaymentStatus.Completed,
      'canceled': PaymentStatus.Cancelled,
      'requires_capture': PaymentStatus.Authorized,
    };

    return mapping[stripeStatus] ?? PaymentStatus.Unknown;
  }

  translateWebhook(event: Stripe.Event): DomainEvent | null {
    switch (event.type) {
      case 'payment_intent.succeeded': {
        const intent = event.data.object as Stripe.PaymentIntent;
        return new PaymentCompleted(
          PaymentId.from(intent.id),
          Money.fromCents(intent.amount, intent.currency.toUpperCase())
        );
      }
      case 'payment_intent.payment_failed':
        return null; // or translate to a PaymentFailed domain event
      default:
        return null; // ignore Stripe events your context doesn't care about
    }
  }
}
Open Host Service / Published Language

Expose a well-defined protocol for integration.

flowchart TB
    subgraph OHS["Open Host Service"]
        PL["Published Language\n(REST API, gRPC, Events Schema)"]
        BC["Your Bounded Context"]
    end

    PL --> A["Consumer A"]
    PL --> B["Consumer B"]
    PL --> C["Consumer C"]

    style OHS fill:#3b82f6,stroke:#2563eb,color:white
    style PL fill:#10b981,stroke:#059669,color:white
    style A fill:#6b7280,stroke:#4b5563,color:white
    style B fill:#6b7280,stroke:#4b5563,color:white
    style C fill:#6b7280,stroke:#4b5563,color:white

Context Map Diagram

Visual representation of all bounded contexts and their relationships:

flowchart TB
    Identity["Identity Context\n(Generic - Auth0)"]
    Legacy["Legacy Catalog\n(Legacy)"]
    Sales["Sales Context\n(Core)"]
    Shipping["Shipping Context\n(Supporting)"]
    Billing["Billing Context\n(Supporting)"]
    Stripe["Stripe Gateway\n(Generic)"]

    Identity -->|Conformist| Sales
    Legacy -->|ACL| Sales
    Sales <-->|Customer-Supplier| Shipping
    Sales -->|Open Host Service| Billing
    Billing -->|Conformist| Stripe

    style Identity fill:#6b7280,stroke:#4b5563,color:white
    style Legacy fill:#9ca3af,stroke:#6b7280,color:white
    style Sales fill:#ef4444,stroke:#dc2626,color:white
    style Shipping fill:#f59e0b,stroke:#d97706,color:white
    style Billing fill:#f59e0b,stroke:#d97706,color:white
    style Stripe fill:#6b7280,stroke:#4b5563,color:white

Integration Patterns

Domain Events for Context Integration

interface OrderPlaced {
  eventType: 'sales.order.placed';
  orderId: string;
  customerId: string;
  items: Array<{ productId: string; quantity: number; price: number }>;
  total: number;
  shippingAddress: Address;
  occurredAt: string;
}

class ShippingOrderPlacedHandler {
  async handle(event: OrderPlaced): Promise<void> {
    const shipment = Shipment.create({
      orderId: ShipmentOrderId.from(event.orderId),
      recipient: Recipient.fromAddress(event.shippingAddress),
      packages: this.calculatePackages(event.items),
    });

    await this.shipmentRepository.save(shipment);
  }
}

class BillingOrderPlacedHandler {
  async handle(event: OrderPlaced): Promise<void> {
    const invoice = Invoice.create({
      orderId: InvoiceOrderId.from(event.orderId),
      customerId: BillingCustomerId.from(event.customerId),
      lineItems: event.items.map(item => ({
        description: `Product ${item.productId}`,
        quantity: item.quantity,
        unitPrice: Money.fromNumber(item.price),
      })),
      total: Money.fromNumber(event.total),
    });

    await this.invoiceRepository.save(invoice);
  }
}

Event Schema Registry

Define and version integration event schemas:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://api.company.com/events/sales/order-placed/v1.json",
  "title": "OrderPlaced",
  "description": "Published when an order is successfully placed",
  "type": "object",
  "required": ["eventType", "eventId", "orderId", "occurredAt"],
  "properties": {
    "eventType": { "const": "sales.order.placed" },
    "eventId": { "type": "string", "format": "uuid" },
    "orderId": { "type": "string", "format": "uuid" },
    "customerId": { "type": "string", "format": "uuid" },
    "total": { "type": "number", "minimum": 0 },
    "occurredAt": { "type": "string", "format": "date-time" }
  }
}

Strategic Design Checklist

  • Identify ubiquitous language terms with domain experts
  • Map subdomains (core, supporting, generic)
  • Define bounded context boundaries
  • Document context map with relationships
  • Design anti-corruption layers for external systems
  • Define integration event schemas
  • Ensure each context has its own data store

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.