Service Boundaries: Where to Draw the Line
The hardest design question in microservices isn't 'how do they talk?'. It's 'what is the right thing to be its own service?'
The point of microservices is independent deployability. The cost is a distributed system. The line between "deserves to be a service" and "should be a module inside another service" is where every team's instinct fights its history.
1. The wrong reasons to split
1.1 "Each team gets a service"
Organisational design is real (Conway's Law), but services should not be one-per-engineer or one-per-squad. A service inherits a tax: deploy, monitor, on-call, schema migration, contract maintenance. Splitting too fine multiplies that tax.
1.2 "We want to use a different language"
Polyglot is a side effect, not a reason. If the data is shared, the language doesn't change the boundary.
1.3 "It will be easier to refactor later"
A monolith with clear module boundaries can be split when needed. A premature microservice with leaky boundaries cannot easily be merged — the wire protocol calcifies them.
2. The right reasons to split
2.1 Different rate of change
If module A ships twice a day and module B once a quarter, deploying them together hurts both. The fast one is constrained, the slow one is destabilised. Split.
2.2 Different scale profile
If the recommendation engine needs 20 GPUs and the user service needs 0.1 CPU, putting them in one process is wasteful. Split.
2.3 Different availability requirement
Auth has to be available when checkout is degraded. If they share a deploy, they share an outage. Split.
2.4 Different data ownership
If two pieces of code write the same table, they are the same service. If they write different tables and only read each other's via API, they are different services. The data is the boundary.
3. The "shared database" smell
The #1 way teams end up with the costs of microservices and none of the benefits: two services that both write to the same Postgres tables.
Now a schema change requires both teams to deploy in lockstep. The whole independence story is gone. The right shape:
- Each service owns its tables. Period.
- Other services read via the owning service's API, or via an event stream the owning service emits.
- No cross-service joins in SQL. Ever.
When two teams need the same data, they either pick an owner (and the other becomes a consumer) or the data is genuinely shared and goes into a third "domain service."
4. Cohesion test: would I deploy these together?
Sketch the proposed service boundary. Ask: for every plausible feature in the next 6 months, does it touch only this service, or does it touch this one and another?
If 80% of features cross the boundary, you have drawn it wrong. Either move the boundary or stop pretending these are separate concerns.
5. The carving exercise
For an existing monolith, two techniques work in practice:
5.1 Event storming
Gather domain experts and engineers, post-it-note every business event ("Order placed," "Payment received"), cluster events into aggregates, cluster aggregates into bounded contexts. The contexts often map cleanly to services.
5.2 Strangler fig
Build the new service alongside the monolith. Route a small slice of traffic through it. When confidence is high, expand. When the slice is 100%, delete the monolith code path.
Resist the urge to do big-bang rewrites. They take 2× the estimate and lose features along the way.
6. The smell checklist for "should we merge these?"
- Service A and B are deployed together every time.
- Service A breaks → service B's tests fail.
- A change to service A's data model forces a change to service B's API.
- Service B's monitoring includes service A's health checks.
- A single team owns both.
Three or more of those, and you are paying microservice costs for monolith behaviour. Merge.
The honest part
Get this wrong and you re-platform in 18 months. Get it right and you barely notice the architecture — features ship, services scale independently, on-call is bounded. The decisive variable is not REST vs gRPC, not Kafka vs RabbitMQ, not Postgres vs MongoDB. It is whether your boundaries respect your data and your release rhythm.