A service boundary should keep paying rent.
Two services, one team. Most changes touch both repositories. A cannot be deployed safely unless B follows. When one gets slow, the other starts failing with it. A single checkout request crosses the boundary four times synchronously.
Nobody on that team thinks the architecture is broken. It ships, the dashboards are green most weeks, and nobody has ever proposed merging the two services, because there is no meeting where that proposal would belong. That gap is the thing I want to talk about.
We have a rich vocabulary for splitting software. Extract a service. Find the bounded context. Strangle the monolith. Every step has a named pattern and a migration guide. The other direction has far less, and what is missing is not the writing but the mechanism: a scheduled moment when someone asks whether an old boundary should still exist. For plenty of mature systems that is now the more interesting question. Suspecting you have too many services tells you nothing about which two to combine, how far, or how to defend the decision to a staff engineer who remembers what the split cost.
What a Boundary Buys, and What It Charges
A service boundary buys real things. One part of the system can be deployed safely without waiting on another. Expensive workloads can be scaled or isolated independently. Two teams with different roadmaps stop negotiating every merge.
It also runs a tab that is easy to underestimate, because it arrives in increments. A function call becomes a network call. An internal interface becomes a versioned contract with a deprecation policy. A local transaction becomes a consistency problem you now own. A stack trace becomes a distributed trace, if context propagation survived the last queue hop.
Availability shows up in arithmetic. Two services that each deliver 99.9%, with their failures approximated as independent, give a serial path near 99.8%: about 43 minutes of monthly downtime budget becoming about 86. Neither service got less reliable. The serial dependency changed the reliability of the path, and every extra hop multiplies again. Tail latency compounds in a messier way, since queueing, retries, and correlated load can push the end-to-end p99 well past anything the per-service dashboards suggest.
None of this makes decomposition a bad idea. It means a boundary has to buy something in exchange for what it charges, the exchange rate changes over time even when the code does not, and having been built is not a reason on its own.
That last part matters because decomposition produces a convincing kind of fake progress. Every repo gets easier to read. Every deployment unit shrinks. Local reasoning improves everywhere while global reasoning quietly gets worse, and local reasoning is the only kind most of us do on a Tuesday afternoon.
The Code Got Smaller. The Interaction Surface Got Larger.
Split a checkout component into Pricing, Inventory, and Promotions and you get three boxes where there was one. The boxes got smaller and the arrows got more important, which is the trade nobody puts on the slide. Each arrow can carry latency, retries, failure propagation, contract evolution, and an ownership handoff. Three small boxes and three arrows is not obviously simpler than one medium box. It is three things you can reason about locally and one system whose behavior you cannot reason about that way at all. You have to read the graph instead.
Service count is a weak measure of architecture quality for this reason. The useful questions are about the graph. Which dependencies sit on the highest-traffic paths? Which boundaries change together? Where does slowness propagate? Which services have quietly become hubs that a dozen customer journeys pass through?
Five Signals Worth Investigating
None of these tells you to merge. Each one tells you a specific benefit you assumed you were buying may have stopped arriving. Any one of them is enough to investigate. Several on the same boundary raise the priority and strengthen the case that the boundary itself, rather than one local implementation detail, is what deserves review.
1. Changes arrive in pairs
Dependencies are fine. Every useful system has them. The revealing measurement is change coupling: how often does a change to A force a change to B in the same week? Bidirectional is the interesting case. If a change in either direction routinely creates work in the other, independent evolution is no longer something this boundary provides.
One caution that gets skipped a lot. A wrong place has three fixes: move the misplaced responsibility across, merge, or accept the coordination cost. Merging gets reached for because it is the only one with a name. Re-cutting the seam is worth testing first, and if pricing logic keeps leaking into the inventory service, moving one module may be the entire fix.
2. The boundary taxes the hot path
Some boundaries get chatty. One customer operation crosses the same seam three, four, six times, or a large share of the request budget is spent waiting on the other side.
Two measurements, easily conflated. Call volume: more than three synchronous remote calls in one request is enough to go look. Sync Hop, or serial depth: the number of synchronous stages on the longest customer-visible path, which sets the latency floor. A parallel fan-out to four services counts as four calls but adds one stage, assuming they run concurrently and the caller waits once for the group. A boundary can be wide, deep, or both, and the fix differs. The third trigger is remote wait above 40% of end-to-end latency, measured as wall-clock on the critical path rather than summed across concurrent calls.
I use these as a Chatty Threshold rather than a law. They are the point where I stop assuming and start measuring. Tail Index, P99 over the median, is a separate measurement, but a rising ratio on the same journey corroborates the story: the interaction surface is degrading while local averages still look fine.
The trap is that chattiness is often an interface problem wearing a boundary problem’s clothes. Six crossings per request is usually an N+1 or a missing batch endpoint, so coarsen the interface first. If the pattern survives a proper API, the remaining coupling is structural: the domain may be one thing, or the workflow may be owned on the wrong side. Either way the boundary is the subject, not the endpoint.
3. Failure crosses the boundary anyway
Checkout depends synchronously on Pricing for nearly every request. Pricing gets slow, Checkout threads pile up waiting, retries add load, and customers experience an outage of Checkout. Two processes, one failure domain. This is one common symptom of a distributed monolith.
Now the part that usually gets stated backwards, including in earlier drafts of this post. Merging does not fix it. If pricing computation is slow, it is slow in-process too. What merging removes is the illusion of isolation, plus the thread pool exhaustion and retry amplification the network added on top of the original slowness.
So this signal is a fork rather than a conclusion. Either you actually buy the isolation you thought you had, with bulkheads, timeout hierarchies, retry budgets, and a real fallback for a degraded Pricing, or you stop paying network costs for isolation you never receive. Both are defensible. Paying and not receiving is the only clearly wrong option.
4. One service has become a hub
This is the signal I would put first if the list were ordered by damage.
Start with Dependency Degree, upstream plus downstream, weighted by what moves through: traffic, change frequency, data sensitivity, and how much of the business stops when it stops. Raw caller count treats every edge as equal and flatters the wrong services. Hub risk goes further than degree, rising when high connectivity combines with business criticality, tail stress, and how far a failure propagates. A service with eleven callers, four of them on revenue paths, is shared infrastructure maintained like a feature service, and one bad deploy there can become a cross-journey incident.
A hub is not automatically a merge candidate or even a design failure. Auth, configuration, and payment gateways earn the position, and three interventions compete for any hub worth worrying about: split it further, buffer it with bulkheads and backpressure, or consolidate it. A thick, well-defended hub can be the cheapest of the three.
The distinction worth keeping is governed versus ungoverned. A hub can emerge by design or by accident, and either can be fine. What matters is whether it has an owner, an error budget, containment, and a review mechanism before its connectivity turns into propagation risk.
5. Releases arrive in lockstep
Two pipelines prove nothing on their own. Make it concrete as a Release Coupling Ratio: the share of releases that need coordination with another service or team. The strongest form is lockstep deployment, where A cannot reach production safely unless B goes with it, and this is the one number here that maps directly onto a promise the boundary made when it was created.
A caveat, because this gets conflated constantly. Shipping two features on the same day is not release coupling. You can deploy A and B independently for weeks and flip a flag for both at once, and that is a coordinated product launch, which is fine. The ratio is about what it takes to reach production, not about when a customer sees the result.
This is the signal that most often makes a review unavoidable, and it does not tell you which way to move. Coupled releases are not a compromise architecture. They are a decision point: restore independence, or remove the network boundary. The next section is about choosing.
Not a signal: shared ownership
One team owning both services is not evidence of a bad boundary. Teams own five services all the time and that is normal. Ownership does belong in the analysis from the start, because making responsibility clear is one of the things a boundary exists to do. What it will not do is settle the merge question by itself.
What it does change is the price. When one team owns both sides, merging avoids a cross-team reorganization, though the data migration, runtime, scaling, and security work is unchanged. When two teams own them, all of that still applies and an organizational negotiation with a headcount conversation sits on top. Same technical evidence, materially different project.
The Choice Is Not Two Services or One
Most of this conversation gets stuck because people frame it as a binary. On the release and runtime axis there are three operating states worth distinguishing, plus a deeper recombination that is a separate decision.

Position 1 is what healthy release independence looks like. Two services, two runtimes, and either one can ship without the other. That is one axis rather than a clean bill of health, since a pair can release independently and still share a database or propagate every failure.
Position 2 is a common failure state, and it usually arrives by drift rather than design. The services are still separate processes carrying every bit of network cost that implies, and they can no longer ship apart. It is the state where you pay the full distributed-systems bill and collect none of the release independence the boundary was supposed to provide.
So position 2 is a diagnosis, and it forks two ways.
If runtime isolation or independent scaling still matter, or if team autonomy depends on being able to deploy without waiting, restore release independence and get back to position 1. Fix whatever made the releases dependent: backward-compatible contracts, expand-and-contract schema changes, consumer-driven contract tests in CI, a flag that separates rollout from deploy. Merging here throws away a boundary that is still earning.
If those benefits no longer justify a distributed boundary, and consolidation is acceptable on cost and reversibility, one consolidation path is position 3. Consolidation is a family rather than a single move, with internal libraries and tighter modules at the reversible end and structural merges at the other. Position 3 is the one people usually mean when they say merge, and it is worth naming precisely. The network boundary disappears. The domain boundary does not. The modules stay separate. The relevant schemas and contracts stay explicit. The tests stay. The observability stays. What you stop paying is the distributed-systems tax on a boundary whose remaining benefits no longer justified distribution.
Two kinds of enforcement do different jobs here, and one does not replace the other. Module visibility, import rules, and architecture tests keep the structural coupling honest. Semantic contracts keep the behavioral agreement honest: ordering guarantees, staleness tolerance, idempotency scope, the invariants that a type signature never captured. Removing the network does nothing to the second category, so it has to stay explicit.
Fusing the code and the data completely is possible, and it is where most people’s mental image of “merge” lives, but it is a separate domain-modeling decision rather than the goal of consolidation. It only arises once the module boundary has also stopped carrying useful responsibility. That is a different finding, and acting on it is far less reversible. Jumping straight there from a coupled pair is how a merge becomes the six-month project someone eventually writes a cautionary blog post about.
Coupled services are not a middle ground. They are a decision point.
When Merging Is the Wrong Answer
I have argued for a direction the industry underuses, so it is worth saying where the argument breaks.
Data is often where consolidation gets expensive. Separate schemas with separate migration histories and different consistency requirements make this a data migration project, and the code merge is the easy half. A shared database removes one migration step, but read that as a diagnosis rather than good news: the data boundary eroded some time ago. The post that tells you to merge without mentioning the database is selling you something.
Scaling profiles do not average. A CPU-bound service and a memory-bound one in a single process scale on the worse dimension. If one has a much steeper scaling curve, merging forces both to scale as a unit, and every replica added for the hot component carries the footprint of the quiet one. That alone can justify keeping a boundary that fails every other test here.
Compliance and blast radius. Data residency, PCI or PHI scope, tenant isolation, or credential separation can make a boundary mandatory regardless of how chatty it is. A merged service drops the edge between A and B but inherits the union of their external dependencies, so check you have not just built the hub from signal 4.
Two more to price before you start: availability requirements can differ legitimately, and merging fuses the fate of a component that must survive a partial outage with one that can be down for an hour unnoticed, while the regressions in build time, test duration, cold start, and rollback size are never dealbreakers and always surprises.
Creating a boundary is expensive and removing one is expensive too. The asymmetry is as much psychological as technical: we underprice the ongoing tax of a new boundary because it arrives in instalments nobody signs for, then overprice the reversal once undoing it feels like a loss.
Make the Question Routine
The hard case is never the obviously bad boundary. It is the boundary whose value declined four percent a quarter for three years, which nobody ever observed as a change.
So put it on a calendar as a Consolidation Review. Once a quarter, take your highest-risk boundaries, weighted by traffic, change frequency, incident history, and what breaks when they break, and ask:
- What is this boundary buying right now? Name the benefit: independent deployability, failure isolation, independent scaling, clearer responsibility. If the answer takes more than a sentence, that is data.
- Is the benefit showing up in the telemetry? Release coupling, sync hops, dependency degree, whether an incident in one triggers an incident in the other. Claimed and observed benefits diverge more than anyone expects.
- Would we draw this boundary today?
- What does keeping it cost for another quarter?
- What would it cost to undo?
Splitting those last three matters. “Would we draw it today” removes sunk cost, which is correct: the six months spent on the split is gone regardless of what you decide next. The other two are future costs, and neither is sunk. Keeping the boundary costs the operational tax plus whatever the coordination is slowing down. Undoing it is a live expense that the sunk-cost framing is very good at waving away. A boundary can be one you would never create today and still one you should keep, because the migration costs more than another quarter of friction. That is a defensible answer. It just needs saying out loud rather than arriving by avoidance.
The output does not need to be an architecture project. Keep it, measure it, or schedule the work. Most boundaries land in the first bucket, which is a good outcome, because now they are there on purpose.
If the review does change a boundary, give the decision a sunset date and measure long enough to show the move helped. Release coupling, P95 and P99, incident density, and error-budget burn should improve inside a quarter, or the decision comes back to the same table. A review that only produces decisions is half a loop.
Without a mechanism like this, every boundary becomes permanent by default, and default permanence is not a decision anyone made.
Split When the Boundary Buys Something
Microservices work because boundaries work. A service boundary should keep paying its rent, and independent deployability, failure isolation, scaling flexibility, and clearer responsibility are how it pays.
When those benefits stop arriving, the bill does not stop with them. The network hops remain. The distributed failure modes remain. The coordination overhead very much remains. The semantic contracts remain too, and that one is deliberate: consolidation removes topology that stopped earning without erasing a domain boundary that still means something.
We have gotten very good at taking systems apart. The other skill starts with something much smaller than a merge: a recurring meeting where “should this still be two things” is an ordinary question rather than a career risk.
This essay develops an idea from Chapter 3, “The Illusion of Small Services,” in Beyond Microservices: Building Systems That Align Architecture, Teams, and Flow.

Leave a Reply