Files
next.orly.dev/.claude/skills/domain-driven-design/references/strategic-patterns.md
mleku c9a03db395
Some checks failed
Go / build-and-release (push) Has been cancelled
Fix Blossom CORS headers and add root-level upload routes (v0.36.12)
- Add proper CORS headers for Blossom endpoints including X-SHA-256,
  X-Content-Length, X-Content-Type headers required by blossom-client-sdk
- Add root-level Blossom routes (/upload, /media, /mirror, /report, /list/)
  for clients like Jumble that expect Blossom at root
- Export BaseURLKey from pkg/blossom for use by app handlers
- Make blossomRootHandler return URLs with /blossom prefix so blob
  downloads work via the registered /blossom/ route
- Remove Access-Control-Allow-Credentials header (not needed for * origin)
- Add Access-Control-Expose-Headers for X-Reason and other response headers

Files modified:
- app/blossom.go: Add blossomRootHandler, use exported BaseURLKey
- app/server.go: Add CORS handling for blossom paths, register root routes
- pkg/blossom/server.go: Fix CORS headers, export BaseURLKey
- pkg/blossom/utils.go: Minor formatting
- pkg/version/version: Bump to v0.36.12

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 11:32:52 +01:00

13 KiB

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

  • Linguistic clarity - "Customer" in Sales means something different than "Customer" in Shipping
  • Model isolation - Changes to one model don't cascade across the system
  • Team autonomy - Teams can work independently within their context
  • Focused complexity - Each context solves one set of problems well

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...
Catalog N/A Displayable item with description, images, categories
Inventory N/A Stock keeping unit with quantity and location
Sales Shopping cart ready for checkout Line item with price
Fulfillment Shipment to be picked and packed Physical item to ship
Billing Invoice to collect payment Taxable 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:

  • Unique to this business
  • Complex, requires deep expertise
  • Frequently changing as business evolves
  • Worth significant investment

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

Supporting Subdomain

Necessary for the business but not a differentiator.

Characteristics:

  • Important but not unique
  • Moderate complexity
  • Changes less frequently
  • Custom implementation needed

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

Generic Subdomain

Solved problems with off-the-shelf solutions.

Characteristics:

  • Common across industries
  • Well-understood solutions exist
  • Rarely changes
  • Not a differentiator

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

Example: E-Commerce Platform

Subdomain Type Strategy
Product Recommendation Engine Core In-house, top talent
Inventory Management Supporting Build, adequate investment
Payment Processing Generic Third-party (Stripe, etc.)
User Authentication Generic Third-party or standard library
Shipping Logistics Supporting Build or integrate vendor
Customer Analytics Core In-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

Term Definition Context
Order A confirmed purchase with payment collected Sales
Shipment Physical package(s) sent to fulfill an order Fulfillment
SKU Stock Keeping Unit; unique identifier for inventory Inventory
Cart Uncommitted collection of items a customer intends to buy Sales
Listing Product displayed for purchase in the catalog Catalog

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:

  • Different parts need to evolve at different speeds
  • Different teams need ownership
  • Model complexity is becoming unmanageable
  • Language conflicts are emerging within the context

Don't split when:

  • Transaction boundaries would become awkward
  • Integration cost outweighs isolation benefit
  • Single team can handle the complexity

When to Merge Bounded Contexts

Merge when:

  • Integration overhead is excessive
  • Same team owns both
  • Models are converging naturally
  • Separate contexts create artificial complexity

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