Service boundaries
Why clear service boundaries are the most important part of microservices — and how to draw them without building a distributed monolith.
If there is one decision that decides whether microservices help or hurt, it is where each service begins and ends.
Everything else — APIs, Kafka topics, Kubernetes, observability — sits on top of that line. Draw the boundary wrong and you get a distributed monolith: many deployables, shared databases, chatty calls, and releases that still move as one.
What a boundary actually means
A good service boundary owns a business capability end to end. It owns:
- the behavior for that capability (rules, workflows, invariants)
- the data required to enforce those invariants
- the API (or events) other systems use to talk to it
It does not own “all the Java code for payments,” or “everything that touches Postgres.” Technology layers are poor boundaries. Domain ownership is the point.
In payment systems this shows up clearly: orchestration, risk, reconciliation, and PSP adapters each have different change rates, failure modes, and data they must protect. Mixing them into one “payments service” looks simple until one change forces a risky release for all of them.
Why boundaries come first
Independent deployability only works if services can change without coordinated releases. That requires each service to own its data and avoid reaching into another service’s tables.
Failure isolation only works if a slow or down dependency does not freeze the whole platform. Clear boundaries make timeouts, retries, and fallbacks meaningful instead of speculative.
Team ownership follows the same cut. Conway’s law is not a slogan here — if two teams share one database and one release train, you do not have microservice autonomy, you have shared fate.
How to draw them
Start from capabilities and invariants, not from endpoints.
- Name the business capability in plain language (“route a payment to a PSP,” “reconcile settlement files,” “issue a refund”).
- List the data that must stay consistent for that capability to be correct.
- Put that data behind one service. Other services may request or subscribe; they do not update those tables.
- Prefer asynchronous events when you need to notify others without coupling release cycles.
- Keep synchronous calls short, versioned, and intentional — not as a substitute for shared storage.
If two capabilities always change together and share the same invariants, they probably belong in one service. Microservices are a tool for independent change, not a goal to maximize the number of boxes.
A practical smell test
Ask of any proposed split:
- Can this service deploy on its own without a coordinated lockstep release?
- Does it own the data for its invariants?
- If it is unavailable, is the blast radius understood and limited?
If the answers are no, the boundary is not ready — fix that before adding more services.
Clear boundaries are the most important aspect of microservices because they are the only way the rest of the architecture earns its complexity.