How do you find service boundaries?
Not in a workshop with a whiteboard. Boundaries drawn from a domain diagram tend to follow nouns, and nouns produce services that every feature has to call, which is the most expensive mistake available in this architecture. The boundaries that hold up are found by looking at what actually changes together in your codebase and where the same word means two different things.
What makes a boundary good?
A good boundary is one that most changes do not cross. That is the entire test, and it is measurable rather than aesthetic. If a typical feature request can be built, tested and deployed inside one service, the boundary is doing its job. If most features require edits in three services and a coordinated release, the boundary is costing you and returning nothing.
This is why boundaries follow capabilities rather than data structures. Checkout, fulfilment, pricing and settlement are things the business does, and a change to how pricing works tends to stay inside pricing. Customer, product and order are things the business has, and every capability touches all of them.
The second test is decision ownership. If one group of people can change the rules inside a boundary without asking anyone, that boundary matches how the organisation actually works, and the service will be able to deploy independently. If three departments have opinions about the same logic, splitting it into a service does not give anyone autonomy.
Why do entity-shaped services fail?
Because they contain data but no decisions. A user service that offers create, read, update and delete has no behaviour of its own, so every actual business rule lives in a caller, and the caller needs several such services to do anything. The result is a system where a single user action produces a dozen synchronous hops, each with its own failure and latency.
It also produces the worst possible change pattern. Adding a field to a business process means changing the entity service, changing every consumer that reads the new field, and deploying them in a specific order because the schema and the readers cannot move at once. The split has converted a one-line change into a release plan.
The tell is easy to spot in a design document: if a service's interface is mostly getters and setters, it is a shared database with extra latency. A service should expose operations that mean something to the business, and the interesting question about any proposed service is what it decides, not what it stores.
Which signals actually indicate a boundary?
Four are reliable, and the two most reliable are already sitting in your repository. Change coupling and vocabulary conflict can be measured today; the others require judgement.
| Signal | What it looks like | How to check it |
|---|---|---|
| Change coupling | Two areas of code that always appear in the same commit | Group the last year of commits by files touched together |
| Vocabulary conflict | The same word meaning different things to different teams | Ask two teams to define 'customer' or 'order' separately |
| Consistency requirement | Two writes that must both succeed or both fail | List operations that currently rely on one transaction |
| Decision ownership | One group can change the rules without consulting others | Ask who signs off a change to that logic |
| Rate of change | One area rewritten monthly next to one untouched for years | Commit counts per directory over twelve months |
What do you do about concepts every service needs?
Give each service its own version of the concept and share only the identifier. Billing needs a customer with a tax status and a payment method. Support needs a customer with a contact history and a service tier. These are not the same object and forcing them into one shared model recreates the coupling the split was meant to remove.
The mechanism is that one service owns the identity and issues the identifier, and every other service keeps whatever attributes it needs against that identifier. Duplication of a name or an email across services is not a defect here. It is the price of independence, and it is much cheaper than the alternative, which is a central model that no team can change alone.
The pattern to refuse is the shared domain library, a package of common entities imported by every service. It looks like sensible reuse and it functions as a distributed monolith, because upgrading it requires every service to redeploy. Share contracts and identifiers, not models.
How big should a service be?
Big enough that a typical change fits inside it, small enough that one team can hold all of it in mind. Line counts and the phrase 'micro' are unhelpful here; there are perfectly good services of thirty thousand lines and terrible ones of three hundred.
The practical upper bound is comprehension. When nobody can explain what the service does without a diagram, and changes routinely have effects nobody predicted, it is too big and the internal seams will already be visible in the code. The practical lower bound is the deploy: if a service cannot be released on its own without something else changing at the same time, it is too small to justify being separate.
Start larger than feels right. Merging two services that turned out to be one is straightforward. Splitting a service you got wrong is straightforward too. Untangling six services that should have been two is the expensive case, and it is the one produced by starting small.
How do you know a boundary is in the wrong place?
Three symptoms, all observable within a month of the split. Features routinely need two or more services changed together, which means the boundary sits in the middle of a capability rather than between two. Second, one service calls another synchronously several times to complete a single request, which means the data and the logic have been separated from each other.
The third is a service that never changes alone. If service B is redeployed every time service A is, they are one unit with a network hop in the middle, and merging them recovers real time immediately.
None of these are reasons for embarrassment; boundaries are hypotheses and some are wrong. What matters is treating a bad boundary as a defect to be fixed rather than a decision to be defended, and keeping the merge cheap enough that fixing it is a normal week's work rather than a programme.
Common questions
- How do you decide where to split a system into services?
- Split where changes stop crossing. The most reliable evidence is your own commit history: group a year of commits by which files changed together and the natural clusters appear without anyone drawing a diagram. Confirm with vocabulary conflicts, meaning places where two teams define the same word differently, since those almost always mark a genuine boundary between two areas of the business.
- Why are entity-based microservices a bad idea?
- A service built around an entity such as user or product holds data but makes no decisions, so every business rule lives in a caller that needs several such services to complete one action. This produces long chains of synchronous calls and turns small changes into coordinated multi-service releases. A service should expose operations that mean something to the business rather than getters and setters.
- How do services share common data like a customer record?
- Each service keeps its own view of the concept and they share only the identifier. Billing holds tax status and payment method against a customer id; support holds contact history and service tier against the same id. Duplicating a name or email across services is acceptable and much cheaper than a shared model that no single team is able to change.
- How big should a microservice be?
- Large enough that a typical change fits inside one service, small enough for one team to understand entirely. Line counts are not a useful measure. The lower bound is deployability: a service that can never be released without another service changing at the same time is too small to be separate. Starting larger than feels right is safer, because merging is easier than untangling.
- What are the signs of a badly placed service boundary?
- Features that routinely require two or more services to change together, a request that needs several synchronous calls between services to complete, and a service that is never deployed on its own. Each indicates the boundary was drawn through the middle of a capability rather than between two capabilities. Merging the services back together is a normal fix, not a failure.