Lesson 58 of 129
Decomposition by Business Capability and Subdomain
Decomposition is the activity of dividing a system into services. The goal is that each service is cohesive, owns its data, and changes independently of the others. Two complementary methods derive boundaries from the business rather than from the code's technical structure: decomposition by business capability and decomposition by subdomain.
Decomposition by business capability
A business capability is something a business does to create value, described by what it achieves, not how. Capabilities are relatively stable: the way a company handles orders changes more often than the fact that it handles orders.
Procedure:
- List the organization's capabilities, using existing business documents, org charts, and interviews with domain experts.
- Arrange them in a hierarchy. Top-level capabilities are broad (Order Management); lower levels are specific (Order Fulfillment, Returns).
- Assign each capability, or group of closely related capabilities, to a service.
- Identify the data each capability owns.
- Identify the interactions between capabilities: which need synchronous answers, and which can be events.
Example capability tree for an online retailer:
graph TD
R["Retail business"] --> Cat["Catalog management"]
R --> Ord["Order management"]
R --> Fin["Payments and billing"]
R --> Log["Fulfillment and logistics"]
R --> Cus["Customer engagement"]
Ord --> Cart["Shopping cart"]
Ord --> Place["Order placement"]
Log --> Inv["Inventory"]
Log --> Ship["Shipping"]
Cus --> Notif["Notifications"]
Cus --> Rev["Reviews"]Decomposition by subdomain
The DDD approach (see domain-driven design and bounded contexts) divides the problem space into subdomains and aims for one bounded context per subdomain, with each context a service candidate.
Procedure:
- Explore the domain with domain experts (for example, through event storming: a workshop that lists domain events on a timeline, then groups them by the concept that produces them).
- Identify where the language changes: where a term changes meaning, a context boundary is likely.
- Classify subdomains as core, supporting, or generic.
- Define a bounded context for each, and a context map for their relationships.
Capability analysis starts from the organization's structure and what it does; subdomain analysis starts from the model and its language. They usually give similar boundaries, and comparing the two results is a check on both.
Worked example: order to shipment
Consider a retailer, with the following sample flow: a customer adds items to a cart, places an order, pays, the warehouse picks and ships, and the customer is notified.
Candidate services and their owned data:
| Service | Capability | Owns | Notes |
|---|---|---|---|
| Catalog | Catalog management | Products, descriptions, categories | Read-heavy, changes independently of orders |
| Cart | Shopping cart | Carts, items | Short-lived data |
| Order | Order management | Orders, order status | Coordinates the order lifecycle |
| Inventory | Inventory | Stock levels, reservations | Strict consistency on stock counts |
| Payment | Payments | Payment attempts, refunds | Regulated; isolate, often generic |
| Shipping | Shipping | Shipments, carrier labels | Integrates with carriers |
| Notification | Notifications | Templates, delivery log | Generic; consumes events |
What each context calls "product" differs: Catalog holds descriptions, Inventory holds SKUs and quantities, Order holds a snapshot of name and price at purchase time. The identifier (SKU) is the link between them; each service keeps only the attributes it needs.
The flow uses events at boundaries where the caller does not need to wait:
sequenceDiagram
participant C as Customer
participant O as Order
participant I as Inventory
participant P as Payment
participant S as Shipping
participant N as Notification
C->>O: Place order
O->>I: Reserve stock (command)
I-->>O: StockReserved (event)
O->>P: Charge payment (command)
P-->>O: PaymentCompleted (event)
O-->>S: OrderConfirmed (event)
S-->>N: ShipmentDispatched (event)
N->>C: NotificationThe multi-step process across Order, Inventory, and Payment is coordinated with a saga. If the payment fails after stock is reserved, no single transaction can roll back both services, so the Order service sends a compensating command that releases the reservation.
Verbs versus nouns
A service can be defined around a noun (an entity, such as "Customer service" or "Product service") or a verb (an activity, such as "Checkout" or "Shipping").
- Noun services tend to become CRUD wrappers around one table. Every feature that involves a customer then touches the Customer service, so it becomes a shared dependency, and behavior that belongs elsewhere accumulates in it.
- Capability (verb-oriented) services keep the behavior together with the data that behavior needs. The same real-world entity appears in several services in different forms.
An entity name can still serve as a service name when the service owns a capability with real behavior. The test is whether the service holds business rules and owns its data, or is only a data-access layer.
Common wrong splits
| Wrong split | Problem |
|---|---|
| By technical layer (UI service, business logic service, data service) | Every feature change crosses all layers, so all services change together |
| One service per database table or per entity | Chatty calls, no cohesive behavior, distributed transactions for simple operations |
| By development phase or by team convenience | Boundaries have no relation to change patterns |
| A "common" or "utility" service holding shared logic | Becomes a dependency for everything; a single point of coupling and failure |
| Splitting a single aggregate across services | The invariant can no longer be enforced in one transaction |
| Splitting before the domain is understood | Boundaries move later, at high cost |
| Shared database with services split only in code | Schema coupling remains, so services still cannot deploy independently |
Validating boundaries
- Do most feature changes touch only one service?
- Can the service be deployed without coordinating with others?
- Does it own its data, with no other service reading its tables?
- Does one team own it end to end?
- Can it answer its main requests without synchronous calls to many others?
If the answer to several is no, the boundary is a candidate for revision (see service granularity).
Interview questions
- How does decomposition by business capability work? Identify what the business does, arrange capabilities in a hierarchy, and assign each capability with its data to a service. Capabilities are stable, so the boundaries are stable.
- Why are services organized by technical layer a bad split? A feature typically needs changes in every layer, so each change requires coordinated changes and releases across services.
- Why can a service per entity be a problem? Such services become thin data wrappers, behavior spanning entities requires many cross-service calls, and central entities (customer, product) become dependencies of all other services.
- What is event storming used for here? It exposes domain events and where the language changes, which points to bounded context boundaries.
