Architecture patterns
Database per service
Why each service should own its data, what a shared database costs, and how to live with duplicated data, no cross-service joins and no cross-service transactions.
Database per service means each service owns its data. Other services read or change that data only through the owner's API or its events. It is the rule most often broken, and breaking it quietly undoes the benefit of splitting.
The big idea
A shared database looks convenient, but it becomes the worst API you could build. Suppose Orders and Billing both read and write the customers table. Neither team can change the schema without coordinating with the other. A slow query from Billing slows Orders. Nobody knows which service enforces which rule. The schema has become the interface: untyped, unversioned, and almost impossible to evolve.
Owning the data gives a clear contract. The owner decides what the data means, how it is stored, and when it changes.
Walking through it
Take the request "show orders with customer names". With separate ownership, the Orders service can't join against a customers table. It has two options. It can call the Customers service and join the results in memory. Or it can keep a local copy of the fields it needs, fed by events. The second is usually better:
GET /orders?userId=42
1. Orders reads its own table: order 881, customer 42, name "Ana Rao" (copied at order time)
2. Orders returns the page. No call to Customers.
Event flow:
Customers publishes CustomerRenamed(42, "Ana Rao-Patel")
Orders updates its copy for future orders. Old orders keep the name they had.Duplication is often right for the business too. An order should record the address it shipped to, not the customer's current address.
Three costs follow, and each has a plan.
- No cross-service joins. Call the other service and join in memory, or keep the data you need locally.
- Duplicated data. Copies are normal. For each one, decide whether it is a snapshot or a live value.
- No cross-service transactions. An order that reserves inventory and charges a card spans services. Use sagas and the outbox pattern, not one ACID transaction.
"Per service" means logical ownership, not necessarily separate servers. Several small services can share one Postgres cluster, with a schema each and credentials that reach only their own schema.
Common mistakes
- Reading another service's tables "just for a report". It creates coupling that never goes away. Ask the owner for an API or an event.
- Copying data without deciding what the copy means. Decide whether it is a snapshot or a live value.
- Attempting cross-service ACID transactions. Use sagas and outbox events instead.
- Assuming separate servers are required. A schema per service is enough to enforce ownership.
Check yourself
-
Why is a shared
customerstable worse than an API?Schema changes need coordination, one service's slow query hits the other, and nobody can say which service enforces which rule.
-
Why should an order store the shipping address rather than look up the customer's current one?
The order should record where it actually shipped. The customer's current address may have changed since.
Key takeaways
- Each service owns its data, and other services use its API or events.
- A shared database turns the schema into an unversioned, hard-to-change API.
- Duplicated data is normal. Decide whether each copy is a snapshot or a live value.
- Cross-service transactions need sagas and the outbox, not one ACID transaction.
Further learning
Next: the operational tax of microservices, which covers what must be in place before you split.
Related
Monoliths and microservices
When to split a system, how to draw service boundaries, and the operational cost that microservices add.
Distributed transactions
Keeping data consistent when one business operation spans several databases or services, using two-phase commit, sagas, the transactional outbox and reservations, and redrawing boundaries first.
Event-driven architecture, CQRS and event sourcing
Building systems around events, from change data capture to event sourcing and read models, and knowing when it simplifies a system versus creating a distributed mess.