Domain-Driven Design & Service Boundaries

📘 Chapter 10: Microservices & Service Architecture ⏱️ 10 min read 🏗️ Lesson 041

The hardest problem in microservices isn't technology — it's where to draw the boundaries. Split too fine and you get a distributed monolith drowning in network calls. Split wrong and services constantly need coordinated changes. Domain-Driven Design (DDD) provides the conceptual toolkit to find natural service boundaries.

Bounded Contexts

The Core Concept

A Bounded Context is a boundary within which a particular domain model is consistent and meaningful. Inside a context, terms have precise definitions. Across contexts, the same word may mean different things.

Example: "Product" in Catalog means name, description, images, price. "Product" in Inventory means SKU, warehouse location, quantity on hand. Same word, different models — different bounded contexts.

E-Commerce Domain — Bounded Contexts Orders Order, LineItem OrderStatus, Discount ShippingAddress owns: order lifecycle Payments Payment, Refund PaymentMethod, Invoice Transaction owns: money movement Inventory StockItem, Warehouse Reservation, Restock SKU, Location owns: stock levels Shipping Shipment, Carrier TrackingNumber, Label DeliveryEstimate owns: fulfillment Users Account, Profile Credentials, Preferences Address, Role owns: identity & auth
Figure 1: An e-commerce domain split into bounded contexts. Each context owns its model and data — "Product" means something different in Inventory vs Orders.

Ubiquitous Language

Each bounded context has its own vocabulary — the ubiquitous language. This is critical because it prevents confusion:

TermIn CatalogIn InventoryIn Shipping
ProductName, images, price, descriptionSKU, quantity, locationWeight, dimensions, fragility
CustomerBrowsing history, preferencesN/A (doesn't exist)Delivery address, availability
OrderN/AReservation to decrementPackage to deliver

Trying to create one unified "Product" model that satisfies all contexts leads to a bloated, confusing god-object.

Aggregates

Consistency Boundaries Within a Context

An Aggregate is a cluster of domain objects treated as a single unit for data changes. It has a root entity that controls access and enforces invariants.

  • Order aggregate: Order (root) + LineItems + ShippingAddress. All change together atomically.
  • Rule: Only modify one aggregate per transaction. Cross-aggregate consistency is eventual.
  • Service boundary hint: Aggregates that always change together likely belong in the same service.

Context Mapping

How Contexts Relate

  • Shared Kernel: Two contexts share a small subset of the model (risky — tight coupling)
  • Customer-Supplier: Upstream context (supplier) provides data/events that downstream (customer) consumes
  • Anti-Corruption Layer (ACL): A translation layer that protects your model from another context's model
  • Conformist: Downstream adopts upstream's model as-is (easy but reduces autonomy)
  • Published Language: A shared schema (e.g., protobuf, JSON schema) for communication
Context Map — Relationships Orders Payments Inventory Users Customer–Supplier ACL Conformist ACL = Anti-Corruption Layer (translation boundary). Customer–Supplier = upstream provides, downstream consumes.
Figure 2: Context map showing how bounded contexts relate. The ACL protects Inventory from changes in Order's model.

Heuristics for Finding Boundaries

Use these questions to discover natural service boundaries:

  • Team ownership: Could one team own this end-to-end? (Conway's Law: system structure mirrors org structure)
  • Data ownership: Is there a clear set of data that "belongs" here and nowhere else?
  • Rate of change: Does this part change independently from the rest? Different release cadences suggest different services.
  • Business capability: Does it map to a distinct business function? (Payments, Search, Recommendations)
  • Coupling test: If I change this service, do I usually need to change another? If yes → probably same service.

Real-World Examples

🏢 Amazon — Two-Pizza Teams = Bounded Contexts

Amazon's famous organizational principle maps directly to DDD:

  • Each team (small enough to feed with two pizzas, ~6-8 people) owns a service
  • The service boundary = the team's bounded context = a business capability
  • Teams have full autonomy: own code, own data, own deployment pipeline, own on-call
  • Communication between teams mirrors communication between services — via well-defined APIs
  • Result: thousands of services, each owned by a team that understands its domain deeply

🏢 Spotify — Squads Aligned with Service Boundaries

Spotify's squad model (before reorganization) demonstrated DDD principles:

  • Squads: autonomous teams owning a feature area (Search, Playlist, Payments)
  • Each squad owned services that mapped to their bounded context
  • Tribes: groups of squads in related areas (analogous to sub-domains)
  • Key learning: when squad boundaries didn't match service boundaries, coordination overhead exploded

Common Mistakes

⚠️ Boundary Anti-Patterns

  • Nano-services: Too fine-grained — a "UserNameService" that just stores names. Creates massive network overhead for trivial operations.
  • Splitting by technical layer: A "Database Service," "API Service," "UI Service" — this is layered architecture distributed over a network. Every feature change touches all services.
  • Ignoring data coupling: Two services that constantly JOIN each other's data probably shouldn't be separate.
  • Premature splitting: Extracting services before understanding the domain leads to wrong boundaries that are expensive to fix.

Interactive: Identify Bounded Contexts

Scenario: You're building a food delivery platform (like DoorDash). Drag concepts into the bounded context where they belong:

🛒 Ordering
🍕 Restaurant
🚗 Delivery
💳 Payments