Own a capability
Ordering owns order lifecycle rules, not just order tables or order endpoints.
Teams often split systems by controllers, repositories, shared tables, or organization chart lines. Those decompositions feel fast at first but create constant cross-service coordination later. A stronger starting point is subdomain and capability: ordering, pricing, inventory, payments, fulfillment, and notifications each have different language, rules, and change rhythms.
In an Order Management system, the ordering team should not need to understand the internals of shipment routing, and the shipment team should not depend on pricing rules to evolve. Good decomposition creates local reasoning and reduces the number of cross-boundary decisions required for normal change.
Decomposition by subdomain uses domain language to define service boundaries. Core capabilities protect the business differentiators. Supporting capabilities make core work possible. Generic capabilities are important but not unique to the business. A service boundary is healthy when it has coherent rules, clear ownership, and a low need for synchronous chatter with neighbors.
Capability-based decomposition is not the same as "one noun, one service." The right boundary often contains several closely related entities because the real question is transactional and conceptual cohesion, not vocabulary count.
Ordering owns order lifecycle rules, not just order tables or order endpoints.
If a rule changes together, the model and data that support it likely belong together.
A 'user-service' or 'database-service' split often hides technical seams inside business workflows.
| Question | Healthy signal | Warning sign |
|---|---|---|
| Does this capability have its own language? | Ordering, payment, fulfillment terms differ naturally | Only technical nouns separate the services |
| Can one team own the rules? | Changes stay mostly local | Every change crosses multiple teams |
| Are the workflows mostly cohesive? | Core transactions stay inside one boundary | Every command triggers many synchronous calls |
| Can the data evolve independently? | Schema and behavior change together | Services still share conceptual ownership of the same data |
package org.javaomnibus.orders.application;
public interface InventoryReservationPort {
ReservationResult reserve(OrderId orderId, List<OrderLine> lines);
}
public interface PaymentCommandPort {
AuthorizationResult authorize(OrderId orderId, Money amount);
}
public interface ShipmentPlanningPort {
ShipmentPlan planShipment(OrderId orderId, DeliveryAddress address);
}