How Twitter's 'Order' Became Three Different Things
In 2013, Twitter's ads platform had a problem. The word 'campaign' meant something different to every team. For the sales team, a campaign was a deal with a budget and schedule. For the ad-serving team, it was a targeting ruleset. For the billing team, it was a revenue line item. They had one Campaign table trying to serve all three, and every schema change required coordinating 4 teams. The codebase was littered with if-statements checking which 'type' of campaign it was. When they finally applied DDD principles and split into bounded contexts - each with its own Campaign model - deploy velocity tripled. This is the core insight of Domain-Driven Design: the same word often means fundamentally different things in different parts of your business, and forcing a single model creates distributed monoliths.
Bounded Contexts: The Core Concept
The Same Word, Different Meanings:
E-Commerce Example - What is an 'Order'?
Shopping Context: Fulfillment Context: Billing Context:
+------------------+ +------------------+ +------------------+
| Order | | Order | | Order |
| - cart_items[] | | - shipment_id | | - invoice_id |
| - coupon_code | | - warehouse | | - total_amount |
| - shipping_addr | | - tracking_num | | - payment_method |
| - estimated_cost | | - weight_kg | | - tax_amount |
| - customer_id | | - carrier | | - refund_status |
+------------------+ +------------------+ +------------------+
| | |
Cares about: Cares about: Cares about:
What did they buy? How do we ship it? How do we charge?
WRONG: One giant Order table with 40 columns serving all teams.
RIGHT: Three separate Order models, each with only its own fields.
Linked by order_id but completely independent schemas.
Context Mapping: How Contexts Relate
Context Map (relationships between bounded contexts):
[Shopping Context] [Fulfillment Context]
| |
|--OrderPlaced event-------->| (Async event)
| |
| [Billing Context]
| |
|--OrderPlaced event-------->| (Async event)
Relationship Patterns:
Pattern Description Example
---------------- ---------------------------- ----------------
Shared Kernel Two contexts share a small User ID format
piece of model (risky!) shared by all
Customer-Supplier One context provides data Catalog -> Search
another consumes (upstream/ (Catalog is upstream,
downstream) Search is downstream)
Anti-Corruption Downstream builds a translation Legacy billing API
Layer (ACL) layer to protect its model wrapped in clean
from upstream's model interface
Published Upstream publishes formal API Public REST API
Language that all downstreams consume with versioning
Conformist Downstream accepts upstream's Using Stripe's
model as-is (no translation) data model directly
Finding the Right Boundaries
Four Signals That Reveal Boundaries:
1. LANGUAGE DIVERGENCE
When teams use the same word differently, you found a boundary.
'Customer' to Sales = lead with budget
'Customer' to Support = ticket history
'Customer' to Billing = payment profile
--> Three bounded contexts.
2. ORGANIZATIONAL STRUCTURE (Conway's Law)
Your architecture will mirror your team structure.
Team A owns Users --> User Service
Team B owns Orders --> Order Service
Team C owns Payments --> Payment Service
Fighting Conway's Law rarely works.
3. RATE OF CHANGE
Pricing rules change weekly.
User registration changes yearly.
--> Separate them so pricing deploys don't risk auth bugs.
4. DATA OWNERSHIP
Ask: 'Who is the single source of truth for this data?'
User profile? --> User Service owns it.
Order status? --> Order Service owns it.
If two services both 'own' the same data, your boundaries are wrong.
Common Boundary Mistakes
Anti-Pattern: Distributed Monolith
Symptom: Every request touches 5+ services.
[Service A] --> [Service B] --> [Service C] --> [Service D]
\ /
\--------> [Service E] ---> [Service F] ---/
Cause: Services split by TECHNICAL layer (API, logic, data)
instead of by BUSINESS capability.
WRONG Split (by layer): RIGHT Split (by capability):
+-------------------+ +-------------------+
| API Service | | User Service |
| (all endpoints) | | (signup, profile, |
+-------------------+ | auth, settings) |
| Business Logic | +-------------------+
| Service | | Order Service |
+-------------------+ | (cart, checkout, |
| Data Service | | order history) |
| (all DB access) | +-------------------+
+-------------------+ | Payment Service |
Every request goes | (charge, refund, |
through all 3! | invoices) |
(Distributed monolith) +-------------------+
Each handles full request
independently.
The Ubiquitous Language
Within each bounded context, everyone - developers, product managers, domain experts - uses the same terminology. If the billing team says 'invoice' and the shipping team says 'packing slip,' those are different concepts in different contexts. Do not force them into a shared model. The code should use the exact words the business uses: no 'AbstractPaymentProcessorFactory' when the business says 'charge the customer.'
Interview Tip
DDD questions are really about service decomposition. When asked 'How would you split this monolith into microservices?', use bounded contexts: (1) Identify where the same word means different things to different teams - those are your context boundaries. (2) Check Conway's Law - align services with team ownership. (3) Verify each service can handle its common operations without calling other services. (4) Use events (not synchronous calls) between contexts. If you mention the Anti-Corruption Layer pattern when integrating with legacy systems, that shows real DDD depth. The biggest red flag is splitting by technical layer (API/Logic/Data) instead of business capability.
Key Takeaway
DDD's bounded contexts tell you where to draw service boundaries: around distinct business capabilities with their own data, language, and models. Look for language divergence (same word, different meanings), organizational boundaries (Conway's Law), different rates of change, and clear data ownership. Services that share too much data or vocabulary are probably one bounded context incorrectly split. Split by business capability, not technical layer, to avoid distributed monoliths.