Strategic DDD and Service Decomposition
Advanced patterns for complex domains and large-scale microservices architectures.
Context Mapping Patterns
Define relationships between bounded contexts:
| Pattern |
Relationship |
When to Use |
| Shared Kernel |
Two contexts share common code |
Very closely related domains, same team |
| Customer-Supplier |
Upstream provides, downstream consumes |
Clear provider-consumer relationship |
| Conformist |
Downstream conforms to upstream model |
Upstream is external/unchangeable |
| Anti-Corruption Layer |
Translation layer protects your model |
Integrate with legacy or external systems |
| Published Language |
Well-documented common language |
Public APIs, integration points |
# Your Clean Domain Model
class Order:
def __init__(self, order_id, customer, items):
self.order_id = order_id
self.customer = customer
self.items = items
# Legacy System has messy model
class LegacyOrderData:
# Fields use different names, structures
ORD_NUM: str
CUST_CODE: str
LINE_ITEMS: str # Comma-separated!
# Anti-Corruption Layer translates
class LegacyOrderAdapter:
def to_domain_model(self, legacy_order: LegacyOrderData) -> Order:
# Translate legacy to clean model
customer = self.customer_service.get_by_code(
legacy_order.CUST_CODE
)
items = self._parse_line_items(legacy_order.LINE_ITEMS)
return Order(
order_id=legacy_order.ORD_NUM,
customer=customer,
items=items
)
def to_legacy_model(self, order: Order) -> LegacyOrderData:
# Translate clean model to legacy
return LegacyOrderData(
ORD_NUM=order.order_id,
CUST_CODE=order.customer.code,
LINE_ITEMS=",".join([i.sku for i in order.items])
)
Event Storming for Service Boundaries
Event Storming is a workshop technique to discover domain events and service boundaries:
- Identify Domain Events: What happens in your business? (OrderPlaced, PaymentProcessed, ItemShipped)
- Group Related Events: Events that happen together often belong in same service
- Find Aggregates: What entities generate these events?
- Define Bounded Contexts: Draw boundaries around related aggregates
- Map Context Relationships: How do contexts interact?
Subdomain Classification
Not all parts of your system are equally important. DDD categorizes subdomains to guide investment:
| Subdomain Type |
Definition |
Strategy |
Examples |
| Core Domain |
Your competitive advantage, what makes you unique |
Build in-house, invest heavily, best engineers |
Amazon's recommendation engine, Netflix's streaming algorithm |
| Supporting Domain |
Necessary but not differentiating |
Build in-house, but simpler implementations |
Inventory management, order processing |
| Generic Domain |
Common to all businesses, not unique |
Buy off-the-shelf or use open-source |
Authentication, email, payment processing |
# CORE DOMAIN - Competitive Advantage
Product Recommendation Service
├── ML-based personalization
├── Real-time behavior tracking
├── A/B testing framework
└── Custom algorithms (proprietary)
# SUPPORTING DOMAIN - Necessary but standard
Inventory Management Service
├── Stock tracking
├── Warehouse integration
├── Reorder point calculations
└── Standard business logic
# GENERIC DOMAIN - Buy or use existing solutions
Authentication Service → Use Auth0 or Okta
Email Service → Use SendGrid
Payment Processing → Use Stripe
Logging → Use Datadog or ELK Stack
Investment Decision Framework
Core Domain: Invest 60-70% of engineering time
Supporting Domain: Invest 20-30% of engineering time
Generic Domain: Invest 10% or less - use existing solutions
Airbnb's Domain Modeling
Airbnb decomposed their monolith using DDD principles:
Bounded Contexts:
- Listing Context: Property listings, availability, amenities
- Booking Context: Reservations, cancellations, modifications
- Pricing Context: Dynamic pricing, smart pricing algorithms
- Messaging Context: Guest-host communication
- Reviews Context: Ratings, written reviews, trust signals
- Payment Context: Transactions, payouts, currencies
Subdomain Classification:
| Type |
Domains |
| Core |
Pricing algorithms, Trust & Safety, Search & Discovery |
| Supporting |
Booking, Messaging, Listing management |
| Generic |
Authentication (OAuth), Payments (Stripe), Email (SendGrid) |
Result: Each context became an independent microservice owned by a dedicated team. Clear boundaries enabled parallel development and independent deployment.
LinkedIn's Service Boundaries
LinkedIn evolved from a monolithic Rails app to 500+ microservices using DDD.
Major Bounded Contexts:
- Identity Context: User profiles, connections, network graph (1B+ members)
- Feed Context: News feed, content distribution, engagement
- Messaging Context: InMail, chat, notifications
- Jobs Context: Job postings, applications, matching algorithm
- Learning Context: LinkedIn Learning content and progress
- Sales Navigator Context: B2B sales tools and leads
Key Architectural Decisions:
- Data Partitioning: Sharded by member ID across 1000s of database shards
- Caching Strategy: Memcached + local caching for profile data
- Event Streaming: Kafka for real-time data propagation (4+ trillion events/day)
- API Gateway: "Rest.li" framework for consistent API design
Migration Lessons:
- Started with extracting "Identity" service (most critical)
- Built comprehensive monitoring before decomposing
- Used strangler fig pattern over 3+ years
- Maintained backward compatibility throughout migration
Outcome: 100+ development teams working independently, 1000s of deployments per day, 99.99% uptime
Shopify's Service Decomposition
Shopify serves 2M+ merchants with a microservices architecture designed for multi-tenancy.
Core Service Boundaries:
| Service |
Responsibility |
Scale |
| Shop Service |
Store configuration, branding, domains |
2M+ shops |
| Product Service |
Product catalog, variants, inventory |
100M+ products |
| Order Service |
Order processing, fulfillment |
10,000+ orders/minute peak |
| Checkout Service |
Cart, payment processing |
$200B+ GMV annually |
| Shipping Service |
Shipping rates, labels, tracking |
100+ carrier integrations |
DDD Principles Applied:
- Bounded Contexts: Services aligned with merchant mental models
- Ubiquitous Language: API terms match merchant terminology (not technical jargon)
- Anti-Corruption Layers: Protect core from 1000s of third-party app integrations
- Domain Events: Event-driven architecture for webhook system (billions of events)
Multi-Tenancy Strategy:
- Each service handles all shops (not one service per shop)
- Data partitioned by shop_id for isolation
- Resource limits enforced per shop to prevent noisy neighbors
- Separate "Plus" tier services for enterprise customers
Key Insight: Service boundaries follow merchant workflows, not technical layers. A merchant thinks about "Products" and "Orders", not "database" and "API".
Service Granularity Decision Framework
Service is Right Size When:
- Can be owned by one small team (2-pizza team)
- Has single, clear business purpose
- Can be deployed independently
- Data can be contained within service
- Failure doesn't require cascading rollbacks
Service is Too Small When:
- Excessive network chattiness between services
- Difficult to understand transaction flow
- More time spent on coordination than development
- Shared data access patterns across multiple services
Practical Service Boundary Identification
Let's walk through a real example of identifying service boundaries for a social media platform:
DOMAIN: Social Media Platform
========================================
STEP 1: Identify Core Business Capabilities
-------------------------------------------
- User Management (registration, profiles, authentication)
- Content Creation (posts, photos, videos)
- Social Graph (followers, friends, connections)
- Feed Generation (timeline, discovery)
- Notifications (alerts, messages)
- Analytics (engagement metrics, insights)
- Advertising (ad serving, targeting)
STEP 2: Apply Bounded Context Analysis
---------------------------------------
Context: USER IDENTITY
├── Entities: User, Profile, Credentials
├── Data: email, password, name, bio
├── Operations: register(), login(), updateProfile()
└── Team Ownership: Identity Team (5 engineers)
Context: CONTENT
├── Entities: Post, Photo, Video, Comment
├── Data: content_text, media_files, metadata
├── Operations: create(), edit(), delete(), moderate()
└── Team Ownership: Content Team (8 engineers)
Context: SOCIAL GRAPH
├── Entities: Relationship, Follow, Friend
├── Data: follower_id, following_id, relationship_type
├── Operations: follow(), unfollow(), suggestFriends()
└── Team Ownership: Graph Team (6 engineers)
Context: FEED
├── Entities: FeedItem, Timeline
├── Data: Aggregated view of content + graph
├── Operations: generateFeed(), rank(), filter()
└── Team Ownership: Feed Team (10 engineers - complex algorithms)
Context: NOTIFICATIONS
├── Entities: Notification, Alert
├── Data: recipient_id, type, content, read_status
├── Operations: send(), markAsRead(), getUnread()
└── Team Ownership: Notifications Team (4 engineers)
STEP 3: Define Service Boundaries
----------------------------------
✅ GOOD SERVICE BOUNDARIES (follow contexts):
1. User Service
- Auth & Profile management
- Database: users, credentials, profiles
- API: POST /users, GET /users/:id, PUT /users/:id/profile
2. Content Service
- Post/Photo/Video creation and storage
- Database: posts, media, comments
- API: POST /posts, GET /posts/:id, DELETE /posts/:id
3. Social Graph Service
- Relationship management
- Database: relationships (optimized graph database)
- API: POST /users/:id/follow, GET /users/:id/followers
4. Feed Service
- Timeline generation (reads from Content + Graph)
- Database: cached feeds
- API: GET /users/:id/feed
5. Notification Service
- Alert delivery across channels
- Database: notifications
- API: POST /notifications, GET /users/:id/notifications
STEP 4: Validate Boundaries
---------------------------
✓ Each service has single responsibility
✓ Services can deploy independently
✓ One team can own each service
✓ Clear data ownership
✓ Minimal coupling between services
❌ BAD BOUNDARY EXAMPLES:
X "Database Service" - too generic, not a business capability
X "CRUD Service" - technical, not business-focused
X "Helper Service" - vague responsibility
X Combining User + Content - violates single responsibility
Service Decomposition Approaches Comparison
| Approach |
How It Works |
Pros |
Cons |
Best For |
| Domain-Driven Design |
Model business domains, identify bounded contexts |
• Aligns with business • Natural boundaries • Long-term maintainability |
• Requires domain expertise • Steeper learning curve • Time intensive |
Complex business domains, long-term projects |
| Business Capability |
Decompose by what the business does |
• Easy to understand • Stable over time • Aligns with org structure |
• May miss technical boundaries • Can be too coarse-grained |
Enterprise applications, established businesses |
| Strangler Fig |
Gradually extract services from monolith |
• Low risk • Incremental • Learn as you go |
• Slow process • Temporary duplication • Complex during transition |
Migrating from legacy monolith |
| Technical Seams |
Split by technical layers (frontend, backend, data) |
• Quick to implement • Familiar to developers |
• Creates distributed monolith • Tight coupling • Not recommended |
❌ Avoid this approach |
| Data Flow |
Follow data ownership and access patterns |
• Clear data boundaries • Performance focused |
• May not align with business • Can change with requirements |
Data-intensive applications |
class ServiceBoundaryAnalyzer:
"""Helper to evaluate if a proposed service boundary is good"""
def __init__(self, service_name, responsibilities, data_owned, dependencies):
self.name = service_name
self.responsibilities = responsibilities
self.data = data_owned
self.dependencies = dependencies
def analyze(self):
"""Run all validation checks"""
results = {
'single_responsibility': self._check_single_responsibility(),
'data_ownership': self._check_data_ownership(),
'coupling': self._check_coupling(),
'team_ownership': self._check_team_ownership(),
'deployment_independence': self._check_deployment()
}
score = sum(results.values())
max_score = len(results)
return {
'service': self.name,
'score': f"{score}/{max_score}",
'details': results,
'recommendation': self._get_recommendation(score, max_score)
}
def _check_single_responsibility(self):
"""Service should have one clear purpose"""
if len(self.responsibilities) <= 3 and \
all(r.startswith(self.name.split('Service')[0].lower())
for r in self.responsibilities):
return True
return False
def _check_data_ownership(self):
"""Service should own its data"""
return len(self.data) > 0 # Has its own data
def _check_coupling(self):
"""Minimal dependencies on other services"""
return len(self.dependencies) < 5
def _check_team_ownership(self):
"""Can one team (5-10 people) own this?"""
# Heuristic: <5 responsibilities = manageable
return len(self.responsibilities) < 5
def _check_deployment(self):
"""Can deploy without touching other services?"""
# If no shared database, can deploy independently
return 'shared_db' not in self.dependencies
def _get_recommendation(self, score, max_score):
ratio = score / max_score
if ratio >= 0.8:
return "✅ Good service boundary"
elif ratio >= 0.6:
return "⚠️ Acceptable, but could improve"
else:
return "❌ Reconsider this boundary"
# Example usage
order_service = ServiceBoundaryAnalyzer(
service_name="OrderService",
responsibilities=[
"create_order",
"update_order_status",
"get_order_details"
],
data_owned=["orders", "order_items"],
dependencies=["ProductService", "PaymentService"]
)
print(order_service.analyze())
# Output: {'service': 'OrderService', 'score': '5/5',
# 'recommendation': '✅ Good service boundary'}
# Bad example
helper_service = ServiceBoundaryAnalyzer(
service_name="HelperService",
responsibilities=[
"send_email",
"log_events",
"calculate_tax",
"generate_pdf",
"validate_address"
],
data_owned=[],
dependencies=["UserService", "OrderService", "ProductService",
"EmailService", "shared_db"]
)
print(helper_service.analyze())
# Output: {'service': 'HelperService', 'score': '0/5',
# 'recommendation': '❌ Reconsider this boundary'}
Final Checklist: Is This a Good Service?
Before finalizing a service boundary, ask:
- Business Alignment: Does it map to a clear business capability?
- Single Responsibility: Can you describe what it does in one sentence?
- Data Ownership: Does it own and manage its own data?
- Team Ownership: Can one small team (5-10 people) own it completely?
- Independent Deployment: Can you deploy it without coordinating with other teams?
- Clear Interface: Does it expose a well-defined API?
- Loose Coupling: Depends on <5 other services?
- Bounded Context: Models within have consistent meaning?
If you answered "yes" to 7-8 questions: ✅ Excellent service boundary
If you answered "yes" to 5-6 questions: ⚠️ Acceptable, but refine
If you answered "yes" to <5 questions: ❌ Rethink this boundary