Database-per-service as the default, why a shared database between services is a design smell, and the real patterns (API composition, event-driven sync, saga) for querying and writing across ownership boundaries.
Published September 23, 2026
Domain Decomposition draws the service boundaries; this is the follow-up an interviewer almost always asks next: who owns which data, and what happens when two services both need it?
Order Service β owns β orders_db (only Order Service ever writes here)
Inventory Svc β owns β inventory_db (only Inventory Service ever writes here)
Each service should own its own data store exclusively β no other service writes to it directly, ever. This is what makes independent deployability real: if two services shared a database, a schema migration for one could break the other, and neither could evolve its data model independently. A shared database between services is one of the clearest signals a proposed decomposition (Domain Decomposition) hasn't actually separated the services β it's separated the code while leaving the data coupled.
Once data is genuinely partitioned per service, a natural question a candidate needs to answer is: how does the Order page show the customer's name AND their order history AND the item's current stock level, if that data lives in three different services' databases? Three real patterns answer this:
API Gateway / BFF β calls Order Service, Customer Service, Inventory Service in parallel
β composes the three responses into one view for the client
The simplest pattern: a composing layer (an API gateway, or a backend-for-frontend) calls each owning service and assembles the result. Works well when the composition is read-only and the services can tolerate the composing layer's added latency (calling 3 services and waiting for all 3, ideally in parallel not sequentially).
Inventory Service β publishes "StockLevelChanged" event β Order Service subscribes,
keeps its OWN local cached copy of stock level (denormalized, eventually consistent)
When composition-time calls are too slow or too tightly coupling, a service can instead subscribe to events from the owning service and maintain its own local, denormalized, eventually-consistent copy of the data it needs. This trades strong consistency (the copy can be briefly stale) for speed and decoupling (no synchronous call needed at read time) β the same consistency/latency tradeoff that underlies most of distributed systems design.
Place Order saga:
1. Order Service: create order (PENDING)
2. Inventory Service: reserve stock β if fails, Order Service: cancel order (compensating action)
3. Payment Service: charge β if fails, Inventory Service: release stock (compensating action),
Order Service: cancel order (compensating action)
4. Order Service: mark order CONFIRMED
When a single business operation needs to write across multiple services' data (place an order = reserve inventory + charge payment + create order record), there's no single database transaction spanning all three β a saga coordinates the sequence of local transactions, with an explicit compensating action defined for each step to undo it if a later step fails. This is the standard answer to "how do you keep data consistent across service boundaries without a distributed transaction," and it trades ACID atomicity for eventual consistency plus explicit rollback logic the team has to write and maintain.
Q: Why not just use a distributed transaction (two-phase commit) instead of a saga? A: Two-phase commit requires all participants to be available and locks resources across all of them until every participant commits β this creates tight coupling and availability coupling exactly opposite to why services were split apart in the first place; it doesn't scale well across independently-deployed, independently-available services, which is why sagas (accepting eventual consistency) are the standard practical answer instead.
Q: What happens if a compensating action itself fails? A: This is a genuinely hard, real problem β compensating actions need to be designed to be retriable (idempotent) and, in production systems, failures here often require manual intervention or a dead-letter queue with alerting, since an unrecoverable partial saga state is a real operational risk that needs to be surfaced, not silently swallowed.
Q: Does API composition always mean calling all services synchronously and waiting? A: Not necessarily β calls to independent services can be made in parallel (not sequentially) to reduce total latency, and a composing layer can also apply timeouts/fallbacks per service (see Timeout Strategy, Circuit Breaker Pattern) so one slow dependency doesn't block the entire composed response.
Q: How do you decide between event-driven sync and API composition for a specific cross-service read? A: If the data is read far more often than it changes and slight staleness is acceptable, event-driven local caching avoids repeated cross-service calls entirely; if the data changes frequently or staleness is unacceptable for that specific read, real-time API composition is the more correct (if slower) choice β the decision hinges on the read/write ratio and the actual staleness tolerance of that specific use case.