# 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. ```typescript // 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 = { '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 ```typescript // 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 ```typescript // 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