Microservices fail most often not because distribution is hard but because the boundaries were drawn in the wrong place. Services split by noun - a user service, an order service, a product service - look tidy on a diagram and produce a system where every feature touches four services and no team can ship independently. The boundary is the whole decision. Getting it right is central to our digital engineering work.
01The distributed monolith
The characteristic failure is a set of services that must be deployed together, share a database, and cannot be changed independently. You have taken on every cost of distribution - network calls, partial failure, distributed debugging, deployment coordination - and retained every constraint of a monolith.
The diagnostic is simple: can each service be deployed independently, without coordinating with another team, without a synchronised release? If not, the boundaries are not real, and adding more services will make it worse rather than better.
- Services that must deploy together are one service in disguise.
- Shared database tables across services means shared ownership, not a boundary.
- A change requiring simultaneous edits in three repos is a boundary failure.
- If teams block on each other constantly, the split does not match the work.
02Split by data ownership
The most reliable heuristic is that a service owns its data exclusively. No other service reads its tables directly; access happens through its interface. If two services need write access to the same table, they are one service and the boundary is in the wrong place.
This forces the boundary to follow genuine cohesion. Data that changes together, is validated together, and must be transactionally consistent belongs in one service. When a proposed split would require a distributed transaction to maintain an invariant, that is strong evidence the split is wrong - not a signal to reach for a saga.
03Follow the change patterns, not the diagram
The strongest evidence for where boundaries belong is in your commit history. Which parts of the codebase change together? Those are cohesive and belong on the same side of a boundary. Which parts have not changed together in a year? Those are candidates for separation.
This empirical approach outperforms domain modelling done in a room, because it reflects how the system actually evolves rather than how someone imagined it would. It also explains why extracting services from a working monolith produces better boundaries than designing them upfront - you have the evidence.
| Signal | Suggests | Action |
|---|---|---|
| Modules always change together | Same bounded context | Keep together |
| Independent release cadence | Genuine separation | Candidate for extraction |
| Different scaling profile | Distinct resource needs | Candidate for extraction |
| Shared write access to tables | False boundary | Merge or redesign ownership |
| Chatty synchronous calls | Boundary cuts a cohesive flow | Reconsider the split |
| Different compliance scope | Real isolation requirement | Strong case for separation |
04Start with a modular monolith
For most teams the right sequence is a well-structured monolith with enforced internal module boundaries, extracting services only where a specific need justifies it. Modules with explicit interfaces and no cross-module database access give you most of the organisational benefit without network partitions.
Crucially, this preserves the ability to be wrong. Moving a boundary within a monolith is a refactor. Moving it between services is a migration involving data, contracts, deployment and probably downtime. Since early boundary decisions are made with the least information you will ever have, keeping them cheap to change is worth a great deal.
05Legitimate reasons to extract
There are genuinely good reasons, and they share a property: something specific about the component differs from the rest of the system in a way that the shared deployment constrains.
Independent scaling when one component's resource profile differs sharply. Team autonomy when a distinct team needs to ship without coordination. Technology fit when a component genuinely needs a different runtime. Fault isolation when a component's failure must not affect the core. Compliance isolation when data handling requirements differ materially. Notice that 'we are adopting microservices' is not on that list - each extraction should have a specific reason that would be visible if you did not do it.
- Extract for a specific, articulable constraint - not as a default.
- Prove the boundary as a module before making it a network hop.
- Move the data with the service; shared databases defeat the purpose.
- Expect to get some boundaries wrong and plan for merging them back.
Topics
Lena Voss
Lead Architect · SyncTrix
Writes about the engineering decisions behind production systems - architecture, delivery and the trade-offs that only show up at scale.
Building something like this?
SyncTrix engineers AI, SaaS, platform and cloud systems for enterprises and high-growth teams. Tell us what you're shipping and we'll scope it with you.