A backend service should not be split because the codebase has reached a fashionable line count. The useful question is whether a boundary creates meaningful independence. That might mean independent deployment, a clear business capability, different scaling requirements, stronger ownership boundaries or a need to isolate operational risk.
1. Look for a real pressure signal
Good candidates usually appear where the existing architecture creates measurable friction: unrelated teams block each other, deployments have an unnecessarily large blast radius, one capability needs a very different scaling profile, or a domain has a clear ownership boundary. If none of these problems exists, splitting may simply move complexity from code into the network.
checkout.placeOrder(order)
// Later, if independence is valuable:
POST /orders
// The contract becomes the boundary; internal implementation can evolve.
2. Split around capabilities, not tables
A service boundary should represent behaviour and ownership, not just CRUD around a database table. A useful boundary contains the rules needed to perform a meaningful business capability. Starting from tables often creates anemic services that immediately need each other for ordinary operations.
3. Measure how coupled the candidate really is
Ask what the proposed service needs from the rest of the system. Does it share transactions? Does it require synchronous calls to several other services? Does it read another service's database directly? Does every feature require coordinated deployment? High coupling is a warning that the boundary may be wrong or that more refactoring is needed before extraction.
4. Treat data ownership as part of the boundary
A service that owns behaviour but relies on another application's database is not truly independent. Independent services generally need clear ownership of their persistent data and explicit APIs or events for access. Moving only the controller or API layer while leaving shared database coupling behind can create a distributed monolith.
5. Count the operational cost
Every new service can add deployment pipelines, configuration, secrets, monitoring, alerting, network policies, retries, timeouts and on-call responsibility. Distributed systems also introduce latency and partial failure. The team needs enough operational maturity to operate the resulting system safely.
6. Be careful with transactions and consistency
A transaction that was previously an in-process database transaction may become a distributed workflow after extraction. If the business rule requires atomic changes across multiple data owners, a service split can make correctness substantially harder. Design the consistency model explicitly instead of assuming remote calls behave like local function calls.
7. Prefer incremental extraction
A large rewrite is rarely necessary. A safer approach is to identify one capability, define its interface, redirect consumers, move ownership progressively and retire the old path. Each step should leave the architecture in a demonstrably better state.
8. A practical decision test
Split a service when the boundary is clear, the benefit is measurable and the team can operate the new failure modes. Stay in a modular monolith when a split would mainly create network calls between tightly coupled modules. A well-structured monolith is often the better architecture while a product and team are still finding their boundaries.
Engineering references
This article is an original synthesis of established engineering practices. The references below are included for readers who want the underlying technical documentation and deeper treatment.