Every distributed system I have inherited had the same problem, and it was never the technology. The services were fine. The frameworks were fine. The seams between them had been drawn along the lines of whoever happened to be in the room at the start.
A boundary is a prediction
When you split two capabilities into separate services, you are predicting that they will change independently.
When the prediction holds, you get what the diagram promised: teams shipping without coordinating, components scaling separately, failures contained. When it does not hold, you get a distributed monolith — the operational cost of microservices with the coupling of a monolith, which is the worst available outcome.
The prediction is about the business, not the code. Two capabilities change together because a single business decision touches both, and no amount of interface design makes that go away. This is why boundaries drawn from a database schema tend to be wrong, and boundaries drawn from a conversation with the people who run the business tend to be right.
Three signals the seam is in the wrong place
- Two services are always deployed together. Not usually. Always. If neither can go out alone, they are one unit with a network call in the middle.
- A single business change edits three repositories. Feature work that reliably spans the same set of services is describing a capability your architecture does not have.
- One team's backlog is permanently blocked on another's queue. Coordination cost you pay every sprint is a boundary telling you something.
Any one of these on its own is noise. All three together is not a smell, it is a diagnosis.
Cheap instruments beat opinions
The reason bad boundaries persist is that everyone has an anecdote and nobody has a number. Two measurements I have found worth having, both of which come from data you already produce.
Co-change. How often does a single change touch more than one service?
-- A rising trend here is a seam trying to get your attention. Run it
-- quarterly; watch the direction, not the absolute number, which depends
-- entirely on how your teams batch work.
SELECT change_id, COUNT(DISTINCT service) AS services
FROM deploy_events
WHERE occurred_at > NOW() - INTERVAL '90 days'
GROUP BY change_id
HAVING COUNT(DISTINCT service) > 1
ORDER BY services DESC;
Cross-team review latency. How long does a change wait when it needs approval from a team that does not own it? This one is politically awkward because it looks like a measure of people. It is not — it is a measure of how much your boundaries force strangers to talk.
Neither is precise. Both are far better than the alternative, which is the loudest person's intuition.
What to do when you find one
Moving a boundary is expensive, so the honest options are not "fix it" or "live with it".
Merge them. If two services always change and deploy together, collapsing them removes network calls, partial-failure handling, distributed tracing complexity and a deployment coordination problem. This is treated as an admission of failure and it should not be. A boundary that was correct in 2023 can be wrong in 2026 because the business changed, and refusing to redraw it is a more serious error than drawing it wrong in the first place.
Move the capability, not the code. Often the problem is that one service owns a decision that belongs to the other. Relocating that single responsibility is much cheaper than re-partitioning.
Introduce a genuine interface. Sometimes the boundary is right and the contract is bad — leaky, chatty, exposing internals. Fixing the contract is not the same as moving the seam, and mistaking one for the other burns quarters.
Do nothing, deliberately, and write down why. A known-wrong boundary that everyone understands and routes around is manageable. The dangerous ones are the boundaries nobody has noticed.
The organisational half
I have never seen a boundary problem that was purely technical, and I have stopped expecting to.
Systems mirror the communication structures of the organisations that build them — this is old, well-documented, and consistently ignored in practice. If two teams cannot coordinate, their services will not integrate cleanly no matter how good the API design is. If one team owns a service that another team changes constantly, ownership is fiction and the review process is theatre.
Which means the most effective architectural intervention available is sometimes changing who is on which team. That is uncomfortable for an architect, because it is not our decision to make and it does not look like architecture. It is still often the right answer, and the useful move is to bring the evidence — the co-change numbers, the review latency — to whoever does make that decision.
The thing I actually believe
The interesting part of software is never the framework. It is the seams: where services meet, where teams hand off, where a decision made in a hurry on a Tuesday becomes the constraint everyone works around for three years.
Frameworks are interchangeable and we argue about them because the arguments are legible. Boundaries are consequential and we avoid them because getting them wrong is embarrassing and slow to discover. Spend the attention where the consequences are.