🔀 Monolith to Microservices
DDD & Bounded Contexts
Using Domain-Driven Design to find natural service boundaries before you draw them
The most common microservices failure is drawing boundaries around technical layers (Controllers, Services, Repositories) instead of business domains. DDD gives you the language and tools to find where the real boundaries live in the business.
Key Concepts
Bounded Context
A Bounded Context is the boundary within which a specific domain model is consistent and valid. 'Customer' means different things in Sales (lead) vs Shipping (address) — they belong in different contexts.
Ubiquitous Language
Every Bounded Context has its own vocabulary used by both engineers and domain experts. Terms crossing context boundaries need explicit translation via Anti-Corruption Layers.
Context Mapping
Draw explicit relationships between contexts: Shared Kernel (shared code), Customer-Supplier (upstream/downstream), Conformist (accept upstream model), Anti-Corruption Layer (translate).
Aggregate Roots
An Aggregate is a cluster of domain objects treated as a single unit. Only the Aggregate Root can be referenced from outside. Order (root) + OrderItems (entities) + Address (value object).
Bounded Context Example: E-Commerce
Why Context Boundaries Matter
The word Product appears in multiple contexts with completely different meanings. This is the tell-tale sign of a Bounded Context boundary.
| Context | What 'Product' Means | Key Fields |
|---|---|---|
| Catalog Context | Something shown to shoppers | name, description, images, category, SEO slug |
| Inventory Context | Something we have in stock | sku, warehouseLocation, stockCount, reorderLevel |
| Pricing Context | Something with a price rule | basePrice, discounts, taxClass, currency |
| Order Context | Something a customer bought | productId (reference), priceAtPurchase, quantity |
Prisma Schema — Database per Bounded Context
order-service/schema.prismaprisma
1// ORDER SERVICE — its own isolated Prisma schema
2// Does NOT import Product or User models from other services.
3// Uses soft references (foreign keys are just plain IDs).
4
5model Order {
6 id String @id @default(uuid())
7 customerId String // Soft reference — NOT a FK to User service DB
8 status OrderStatus @default(PENDING)
9 totalAmount Decimal @db.Decimal(12, 2)
10 currency String @default("USD")
11 createdAt DateTime @default(now())
12 updatedAt DateTime @updatedAt
13
14 items OrderItem[]
15 @@index([customerId])
16 @@index([status, createdAt])
17}
18
19model OrderItem {
20 id String @id @default(uuid())
21 orderId String
22 productId String // Soft reference — price is copied at purchase time
23 productName String // Denormalized — independent from Catalog service
24 priceSnapshot Decimal @db.Decimal(10, 2)
25 quantity Int
26
27 order Order @relation(fields: [orderId], references: [id])
28}💡
Senior Architect Insight: If two services need to share a database table, they're not separate services — they're modules of the same service. Share data through well-defined APIs and events, never through a shared database.