strategic-patterns.md raw

Strategic DDD Patterns

Strategic DDD patterns address the large-scale structure of a system: how to divide it into bounded contexts, how those contexts relate, and how to prioritize investment across subdomains.

Bounded Context

Definition

A Bounded Context is an explicit boundary within which a domain model exists. Inside the boundary, all terms have specific, unambiguous meanings. The same term may mean different things in different bounded contexts.

Why It Matters

Identification Heuristics

  1. Language divergence - When stakeholders use the same word differently, there's a context boundary
  2. Department boundaries - Organizational structure often mirrors domain structure
  3. Process boundaries - End-to-end business processes often define context edges
  4. Data ownership - Who is the authoritative source for this data?
  5. Change frequency - Parts that change together should stay together

Example: E-Commerce Platform

Context"Order" means..."Product" means...
CatalogN/ADisplayable item with description, images, categories
InventoryN/AStock keeping unit with quantity and location
SalesShopping cart ready for checkoutLine item with price
FulfillmentShipment to be picked and packedPhysical item to ship
BillingInvoice to collect paymentTaxable good

Implementation Patterns

Separate Deployables

Each bounded context as its own service/application.

catalog-service/
├── src/domain/Product.ts
└── src/infrastructure/CatalogRepository.ts

sales-service/
├── src/domain/Product.ts    # Different model!
└── src/domain/Order.ts

Module Boundaries

Bounded contexts as modules within a monolith.

src/
├── catalog/
│   └── domain/Product.ts
├── sales/
│   └── domain/Product.ts    # Different model!
└── shared/
    └── kernel/Money.ts      # Shared kernel

Context Map

Definition

A Context Map is a visual and documented representation of how bounded contexts relate to each other. It makes integration patterns explicit.

Integration Patterns

Partnership

Two contexts develop together with mutual dependencies. Changes are coordinated.

┌─────────────┐     Partnership     ┌─────────────┐
│   Catalog   │◄──────────────────►│  Inventory  │
└─────────────┘                     └─────────────┘

Use when: Two teams must succeed or fail together.

Shared Kernel

A small, shared model that multiple contexts depend on. Changes require agreement from all consumers.

┌─────────────┐                    ┌─────────────┐
│    Sales    │                    │   Billing   │
└──────┬──────┘                    └──────┬──────┘
       │                                  │
       └─────────►  Money  ◄──────────────┘
                   (shared kernel)

Use when: Core concepts genuinely need the same model. Danger: Creates coupling. Keep shared kernels minimal.

Customer-Supplier

Upstream context (supplier) provides data/services; downstream context (customer) consumes. Supplier considers customer needs.

┌─────────────┐                    ┌─────────────┐
│   Catalog   │───── supplies ────►│    Sales    │
│  (upstream) │                    │ (downstream)│
└─────────────┘                    └─────────────┘

Use when: One context clearly serves another, and the supplier is responsive.

Conformist

Downstream adopts upstream's model without negotiation. Upstream doesn't accommodate downstream needs.

┌─────────────┐                    ┌─────────────┐
│ External    │───── dictates ────►│  Our App    │
│    API      │                    │ (conformist)│
└─────────────┘                    └─────────────┘

Use when: Upstream won't change (third-party API), and their model is acceptable.

Anti-Corruption Layer (ACL)

Translation layer that protects a context from external models. Transforms data at the boundary.

┌─────────────┐        ┌───────┐        ┌─────────────┐
│   Legacy    │───────►│  ACL  │───────►│  New System │
│   System    │        └───────┘        └─────────────┘

Use when: Upstream model would pollute downstream; translation is worth the cost.

// Anti-Corruption Layer example
class LegacyOrderAdapter {
  constructor(private legacyApi: LegacyOrderApi) {}

  translateOrder(legacyOrder: LegacyOrder): Order {
    return new Order({
      id: OrderId.from(legacyOrder.order_num),
      customer: this.translateCustomer(legacyOrder.cust_data),
      items: legacyOrder.line_items.map(this.translateLineItem),
      // Transform legacy status codes to domain concepts
      status: this.mapStatus(legacyOrder.stat_cd),
    });
  }

  private mapStatus(legacyCode: string): OrderStatus {
    const mapping: Record<string, OrderStatus> = {
      'OP': OrderStatus.Open,
      'SH': OrderStatus.Shipped,
      'CL': OrderStatus.Closed,
    };
    return mapping[legacyCode] ?? OrderStatus.Unknown;
  }
}

Open Host Service

A context provides a well-defined protocol/API for others to consume.

                    ┌─────────────┐
        ┌──────────►│   Reports   │
        │           └─────────────┘
┌───────┴───────┐   ┌─────────────┐
│ Catalog API   │──►│   Search    │
│ (open host)   │   └─────────────┘
└───────┬───────┘   ┌─────────────┐
        └──────────►│   Partner   │
                    └─────────────┘

Use when: Multiple downstream contexts need access; worth investing in a stable API.

Published Language

A shared language format (schema) for communication between contexts. Often combined with Open Host Service.

Examples: JSON schemas, Protocol Buffers, GraphQL schemas, industry standards (HL7 for healthcare).

Separate Ways

Contexts have no integration. Each solves its needs independently.

Use when: Integration cost exceeds benefit; duplication is acceptable.

Context Map Notation

┌───────────────────────────────────────────────────────────────┐
│                      CONTEXT MAP                              │
├───────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌─────────┐         Partnership          ┌─────────┐        │
│  │ Sales   │◄────────────────────────────►│Inventory│        │
│  │  (U,D)  │                              │  (U,D)  │        │
│  └────┬────┘                              └────┬────┘        │
│       │                                        │              │
│       │ Customer/Supplier                      │              │
│       ▼                                        │              │
│  ┌─────────┐                                   │              │
│  │ Billing │◄──────────────────────────────────┘              │
│  │   (D)   │         Conformist                               │
│  └─────────┘                                                  │
│                                                               │
│  Legend: U = Upstream, D = Downstream                         │
└───────────────────────────────────────────────────────────────┘

Subdomain Classification

Core Domain

The essential differentiator. This is where competitive advantage lives.

Characteristics:

Strategy: Build in-house with best talent. Invest heavily in modeling.

Supporting Subdomain

Necessary for the business but not a differentiator.

Characteristics:

Strategy: Build with adequate (not exceptional) investment. May outsource.

Generic Subdomain

Solved problems with off-the-shelf solutions.

Characteristics:

Strategy: Buy or use open-source. Don't reinvent.

Example: E-Commerce Platform

SubdomainTypeStrategy
Product Recommendation EngineCoreIn-house, top talent
Inventory ManagementSupportingBuild, adequate investment
Payment ProcessingGenericThird-party (Stripe, etc.)
User AuthenticationGenericThird-party or standard library
Shipping LogisticsSupportingBuild or integrate vendor
Customer AnalyticsCoreIn-house, strategic investment

Ubiquitous Language

Definition

A common language shared by developers and domain experts. It appears in conversations, documentation, and code.

Building Ubiquitous Language

  1. Listen to experts - Use their terminology, not technical jargon
  2. Challenge vague terms - "Process the order" → What exactly happens?
  3. Document glossary - Maintain a living dictionary
  4. Enforce in code - Class and method names use the language
  5. Refine continuously - Language evolves with understanding

Language in Code

// Bad: Technical terms
class OrderProcessor {
  handleOrderCreation(data: OrderData): void {
    this.validateData(data);
    this.persistToDatabase(data);
    this.sendNotification(data);
  }
}

// Good: Ubiquitous language
class OrderTaker {
  placeOrder(cart: ShoppingCart): PlacedOrder {
    const order = cart.checkout();
    order.confirmWith(this.paymentGateway);
    this.orderRepository.save(order);
    this.domainEvents.publish(new OrderPlaced(order));
    return order;
  }
}

Glossary Example

TermDefinitionContext
OrderA confirmed purchase with payment collectedSales
ShipmentPhysical package(s) sent to fulfill an orderFulfillment
SKUStock Keeping Unit; unique identifier for inventoryInventory
CartUncommitted collection of items a customer intends to buySales
ListingProduct displayed for purchase in the catalogCatalog

Anti-Pattern: Technical Language Leakage

// Bad: Database terminology leaks into domain
order.setForeignKeyCustomerId(customerId);
order.persist();

// Bad: HTTP concerns leak into domain
order.deserializeFromJson(request.body);
order.setHttpStatus(200);

// Good: Domain language only
order.placeFor(customer);
orderRepository.save(order);

Strategic Design Decisions

When to Split a Bounded Context

Split when:

Don't split when:

When to Merge Bounded Contexts

Merge when:

Dealing with Legacy Systems

  1. Bubble context - New bounded context with ACL to legacy
  2. Strangler fig - Gradually replace legacy feature by feature
  3. Conformist - Accept legacy model if acceptable
  4. Separate ways - Rebuild independently, migrate data later