DECISION RECORDARCHITECTURE

Finding boundaries before splitting services: the decision and what it cost

The reasoning behind finding boundaries before splitting services, including the part we expect to age badly.

FILED
READ
AUTHOR
REF

Written in the form we use internally, because the useful part of a decision record isn't the decision. It's the context that made it reasonable. In two years someone will want to reverse this, and they deserve to know what we knew at the time.

A service boundary in the wrong place costs more than no boundary at all. Find the seams in the domain while everything is still one deployable, and only then decide whether any of them are worth a network hop.

SEAMS ARE FOUND, NOT CHOSEN

The seams in a system are wherever two parts of the business genuinely change on different schedules. You can't pick those from an architecture diagram. You find them by watching which files move together in six months of commits, and by noticing which conversations never involve the same two people.

Draw the boundary somewhere else and you've created a distributed transaction where a function call would have done. Every feature after that pays a tax: two deploys, a migration on each side, and a failure mode where half the change is live.

MODULES FIRST, NETWORK LATER

Enforce the boundary inside one codebase first. Separate modules, no shared tables, communication through an explicit interface. That gets you almost all the design benefit and none of the operational cost, and it's cheap to move if you drew the line in the wrong place.

If the boundary survives a year of features without anyone needing to reach across it, it's real, and you can consider a network. Most don't survive, which is exactly the information you wanted before committing to a deployment topology.

We modelled it at north of 600 deliverables a month and the difference only showed up in the tail. At median load you couldn't tell them apart. At the ninety-ninth percentile, one of them stopped being able to explain itself.

WHAT ACTUALLY JUSTIFIES A SPLIT

There are honest reasons: independent scaling of something genuinely heavy, a compliance boundary that has to be physical, a team that needs to ship without coordinating. Notice that two of the three are organisational. That's normal and worth saying out loud.

"Microservices" as a goal isn't a reason, and neither is a diagram that looks tidier. If you can't name which of the three you're buying, you're paying the cost for nothing.

The deciding factor was regulatory, not technical. Producers have to be able to reconstruct why a given delivery package was handled the way it was, months later, in front of someone unfriendly. That killed two of the three options on the spot.

WHERE IT GOES WRONG

  • A boundary drawn from the org chart, requiring a two-service deploy for every ordinary feature.
  • Two services sharing a database table, which is one service with extra latency.
  • Splitting for scale on a component that turns out to be one percent of load.
  • Distributed transactions across a seam that a function call handled fine last year.

Enforce the boundary as a module first. Earn the network hop later.

CONSEQUENCES WE ACCEPTED

We took a slower first two months in exchange for a system you can still reason about in year three. On an eighteen-month horizon we'd have chosen differently, and we said so at the time.

RELATED
SAME GROUND, DIFFERENT ANGLE
ALL TRANSMISSIONS