service decomposition
what to split, why, and what the network boundary actually costs.
“premature decomposition is the root of all microservices”
decomposition takes one deployable unit and turns it into several that talk over a network. it solves a specific set of problems and introduces a different specific set. the decision is worth making deliberately, because a service boundary is one of the most expensive things to move once it exists.
the three reasons that hold up
almost every defensible split traces back to one of these.
independent deployability. teams shipping without coordinating with everyone else touching the codebase. this is usually the real motivation, and it is a good one. it is also the one most often achieved and then immediately given away by services that must be deployed together.
independent scaling. components with genuinely different resource profiles. a transcoding service wants GPUs; the profile service wants none. this is a real cost argument once the difference is large.
ownership clarity. past a certain size, ownership in a shared codebase gets ambiguous. a service boundary forces the conversation: this team builds it, owns its contract, carries its pager.
"we want microservices" is not on the list. it describes the outcome, not the problem.
what the network boundary costs
fowler's microservice trade-offs is a good survey; the items below are the ones that reliably surprise teams.
every call can now fail in ways function calls could not. it can time out, arrive twice, or succeed while the response is lost. all of
availability multiplies downward. a request touching five services at 99.9% each is looking at roughly 99.5% before anything else goes wrong. the
transactions stop working. the database transaction that kept two tables consistent does not span services. the replacements are all worse: two-phase commit is slow and introduces a coordinator that can block, sagas trade atomicity for compensating actions you have to write and test for every failure point, and the third option is redesigning the operation so it never needs cross-service atomicity. the third is usually right and is a modelling problem, not a plumbing one.
data ownership stops being obvious. if two services need user data, one of them owns it and the other asks, or they share a database and the boundary was decorative. this tension does not resolve cleanly, and "we'll figure out the data model later" is how teams end up with a distributed monolith. services that cannot be deployed, tested, or reasoned about independently while paying the full operational cost of being separate.
the operational floor rises. each service is a pipeline, a dashboard, an alert set, an on-call scope, a service discovery entry. testing needs multiple services running or contract tests to stand in for them. distributed tracing stops being optional, because without it a latency regression is unattributable.
what makes a boundary good
the properties are easy to state and worth checking honestly:
high cohesion inside. the things in the service change together. low coupling across. the interface is small and stable. one team owns it end to end. and it can fail without taking the system with it, which means callers have a defined behavior when it is gone.
the diagnostics for a bad boundary are more useful, because they are observable rather than aspirational. if two services are always deployed together, the deployment coupling survived the extraction and you got none of the benefit. if they share a database table, the data coupling did. if a change in A reliably requires a change in B, the interface is in the wrong place. and if a single request makes dozens of round trips between them, the network overhead has already eaten whatever the split was supposed to buy.
any of those four means the boundary is wrong, not that you need better tooling around it.
extract, do not rewrite
the strangler fig pattern is the low-risk path: route traffic through a facade, move one capability at a time behind it, and let the old implementation shrink until it is gone. the alternative, a parallel rewrite, requires you to freeze or double-implement every change to a system that is still evolving, which is why those projects have the reputation they do.
pick the first extraction for boundary clarity rather than value. something with a clean interface, its own data, and a team ready to own it. the first one is where you build the deployment, observability, and contract-testing machinery, so you want to be debugging that machinery rather than the boundary at the same time.
the cost comes first
the operational investment lands immediately. tooling, observability, pipelines, the on-call structure. the benefit, team velocity that does not degrade with headcount, accrues slowly and only if the boundaries were right.
that ordering is the whole decision. for a small team the overhead exceeds anything decomposition returns. for a large one the coordination cost of a shared codebase exceeds the operational cost of services. between those, it depends on specifics you can measure: how often teams block each other, how much of the deploy queue is unrelated changes waiting, how much capacity is wasted on coarse scaling.
which is the argument for