Designing Service Boundaries
1. Applying Domain-Driven Design
| DDD Concept | Role in Microservices |
|---|---|
| Strategic Design | Identifies bounded contexts → service boundaries |
| Tactical Design | Aggregates, entities, value objects inside a service |
| Ubiquitous Language | API contracts mirror business terms |
| Domain Events | Drive async integration between services |
| Aggregates | Transactional consistency boundary |
2. Defining Bounded Contexts
| Step | Action |
|---|---|
| 1. Event Storming | Workshop to map domain events with experts |
| 2. Identify Pivotal Events | State changes critical to business |
| 3. Group by Language | Where terms have one consistent meaning |
| 4. Draw Boundaries | Each group = candidate context/service |
| 5. Map Relationships | Customer/Supplier, Conformist, ACL, Shared Kernel |
3. Identifying Service Domains
| Domain Type | Treatment | Example |
|---|---|---|
| Core | Build in-house, invest heavily | Fraud detection at a bank |
| Supporting | Build but minimize | Internal admin tools |
| Generic | Buy or use SaaS | Auth (Auth0), Email (SendGrid) |
4. Decomposing by Business Capability
| Capability | Service | Owns |
|---|---|---|
| Order Management | order-service | Orders, line items |
| Inventory | inventory-service | Stock levels, reservations |
| Payments | payment-service | Charges, refunds |
| Shipping | shipping-service | Shipments, carriers |
| Notifications | notification-service | Email/SMS delivery |
5. Decomposing by Subdomain
| Approach | Description |
|---|---|
| By Capability | What the business does (verbs) |
| By Subdomain (DDD) | What the business knows (nouns + experts) |
| When to Pick Subdomain | Complex domain logic, expert-driven |
| When to Pick Capability | Workflow-heavy, process-oriented |
6. Applying Single Responsibility Principle
| Smell | Refactor |
|---|---|
| Service changes for >1 reason | Split along axes of change |
| Multiple unrelated tables | Likely 2 services merged |
| Large team owning one service | Split team and service together |
| High coupling at API surface | Re-evaluate boundary |
7. Determining Service Granularity
| Heuristic | Question |
|---|---|
| Team Size | Can a 2-pizza team own it? |
| Cohesion | Do features change together? |
| Coupling | Few sync calls to others? |
| Data | Owns its data without joins? |
| Lifecycle | Independent deploy cadence? |
8. Avoiding Distributed Monoliths
| Anti-Pattern | Symptom | Fix |
|---|---|---|
| Synchronous Chains | A→B→C→D per request | Async events, choreography |
| Shared Database | Multiple services read same table | API ownership, CDC, replicas |
| Lock-Step Releases | Must deploy services together | Versioned, backward-compatible APIs |
| Shared Models | Common DTO library forces redeploy | Each service owns its DTOs |
| Cross-Service Transactions | Distributed locks/2PC | Sagas, eventual consistency |
9. Using Database per Service Pattern
| Aspect | Detail |
|---|---|
| Goal | Loose coupling, independent schema evolution |
| Implementation | Separate schema, instance, or engine |
| Cross-Service Reads | API calls, read-replicas via CDC, materialized views |
| Cross-Service Writes | Sagas, event-driven workflows |
| Trade-off | No joins, eventual consistency |
10. Managing Service Dependencies
| Dependency Type | Risk | Mitigation |
|---|---|---|
| Runtime (sync) | Cascading failure | Circuit breaker, fallback |
| Runtime (async) | Lag, ordering issues | Idempotent consumers, DLQ |
| Build-time (libs) | Coupled releases | Stable, versioned shared libs |
| Data | Hidden coupling via DB | Database per service |
11. Defining Aggregates and Entities
| Term | Definition |
|---|---|
| Entity | Object with identity (e.g. Customer) |
| Value Object | Immutable, equality by value (Money, Address) |
| Aggregate | Cluster of entities/VOs treated as a unit |
| Aggregate Root | Only entry-point; enforces invariants |
| Repository | Persists and reconstitutes whole aggregates |
Example: Order aggregate (Java 21)
public class Order { // aggregate root
private final OrderId id;
private final List<OrderLine> lines = new ArrayList<>();
private OrderStatus status = OrderStatus.DRAFT;
public void addLine(ProductId productId, int qty, Money price) {
if (status != OrderStatus.DRAFT) throw new IllegalStateException();
lines.add(new OrderLine(productId, qty, price));
}
public Money total() {
return lines.stream().map(OrderLine::subtotal).reduce(Money.ZERO, Money::add);
}
}
12. Using Context Mapping Patterns
| Pattern | Use When |
|---|---|
| Partnership | Two contexts succeed/fail together |
| Shared Kernel | Small shared model, joint ownership |
| Customer/Supplier | Upstream serves downstream's needs |
| Conformist | Downstream conforms to upstream model |
| Anti-Corruption Layer | Translate to protect domain integrity |
| Open Host Service | Public protocol for many consumers |
| Published Language | Stable schema (e.g. JSON Schema, Avro) |
| Separate Ways | No integration; independent solutions |