Estimated reading time: 8 minutes By: Editorial Team Published: February 12, 2024
Overview
Service boundaries determine how independently teams can work, how often deployments break other systems, and how much complexity a developer must hold in their head to make a change. Getting them right is more art than science, but there are clear principles that produce better outcomes than guessing.
Key takeaways
- Boundaries should reflect business capabilities, not technical layers.
- Low coupling between services matters more than high cohesion within them.
- The right boundary size depends on team structure as much as technical concerns.
- Start more coarse-grained than you think you need; split when pain materializes.
What makes a boundary good or bad
A service boundary is good when the teams and systems it separates can evolve independently. It is bad when a change to one service routinely requires changes in another — a pattern called tight coupling.
Signs of a poorly placed boundary:
- A feature requires coordinated deployments across multiple services
- Services share a database table or database schema
- A failing service causes cascading failures in unrelated parts of the system
- A single logical transaction must span multiple service calls
Signs of a well-placed boundary:
- A service can be deployed, scaled, or replaced without other services noticing
- The service's interface is stable; internal implementation can change freely
- A team can ship a feature end-to-end without waiting for another team
Domain-driven design and bounded contexts
The most reliable source of good service boundaries is the problem domain itself. Domain-driven design (DDD) formalizes this through the concept of bounded contexts: areas of the system where a given term has a single, unambiguous meaning.
In an e-commerce system, "order" means different things to inventory management (a list of items to pick), payments (an amount to charge), and shipping (a physical package to dispatch). Each of these represents a natural bounded context — and a candidate service boundary.
Identifying bounded contexts
- Run an event-storming session with domain experts and engineers — map events, commands, and aggregates on a whiteboard
- Look for where terminology diverges or contradicts itself
- Trace where data ownership is ambiguous — who is the source of truth for each entity?
- Note where change rates differ — some parts of the system change daily; others change annually
Conway's Law and team topology
Conway's Law states that systems mirror the communication structure of the organizations that build them. This is not a bug to work around — it is a design constraint to work with.
If two teams need to collaborate constantly to ship a feature, the boundary between their services is in the wrong place. Service boundaries and team boundaries should align.
Practical implications
- Assign ownership of each service to a single team
- Ensure each team can deploy their service without coordinating with another team
- If coordination is unavoidable, prefer a well-defined API contract over shared code or shared data
The monolith-first approach
Splitting a system into services before the domain is well-understood is a common and expensive mistake. A modular monolith — a single deployable unit with clearly separated internal modules — is a safer starting point for most new products.
Benefits of starting with a monolith:
- Refactoring boundaries is cheap when everything is in one codebase
- End-to-end testing is simpler
- Deployment complexity is minimal
- Domain understanding improves with time
Extract a service when a specific module needs to scale independently, needs a different technology stack, or needs to be owned by a dedicated team.
Defining service interfaces
Once a boundary is drawn, the interface across it must be defined before implementation begins.
Interface design principles
- Coarse-grained operations — prefer "place order" over "update item quantity, update shipping address, calculate total" as separate calls
- Stable contracts — version APIs explicitly; never remove a field without a migration period
- Event-based integration — for asynchronous workflows, domain events decouple services more effectively than synchronous API calls
- Avoid chatty interfaces — a feature that requires 10 API calls is a sign the boundary is wrong
Data ownership
Each piece of data should have a single authoritative owner. Other services that need that data should either call the owning service or maintain a local read model derived from events.
Sharing database schemas across service boundaries is the most common source of coupling in service-oriented architectures. Even when it feels expedient, it makes independent deployments impossible because a schema change requires coordinating all services that read from the same table.
Conclusion
Good service boundaries are not discovered by dividing a system along technical lines. They emerge from understanding the business domain, aligning with team structure, and being willing to start coarse and split with purpose. Invest time in boundary design before splitting — it is the cheapest time to get it right.