Backend boundaries are not diagrams. They are agreements about ownership, change, failure, and responsibility, and an agreement only works if the whole team can see it. A boundary is useful only if it still guides decisions when the team is under delivery pressure.
Problem Statement
Most backend systems start with boundaries that look clean and become ambiguous over time. Third-party integrations with other systems arrive. New functionality has to be built from scratch. And usually nobody has a clear picture of what the software will look like in five years.
Every one of those pushes on the boundaries a little, and without a shared basis for the design, each push gets resolved differently.
Where Teams Usually Get It Wrong
Teams often draw boundaries before they know the non-functional requirements. They split services by nouns, repositories, or deployment convenience before deciding who owns the behavior, who pays for change, and what load and availability the system actually has to support.
The opposite mistake is designing for the five-year picture nobody can see yet: building the whole structure to support 100,000 users when the system has 10,000 today.
Principles
- Non-functional requirements come before boundaries.
- Keep the design as simple as possible. You aren't gonna need it.
- Ownership is the first boundary.
- Contracts are more important than folders.
- Shared data is a design smell until proven necessary.
- Integration shape must reflect failure modes.
- A boundary that cannot be tested is not yet real.
- Agreements are visible to the whole team.
- Leave the code better than you found it.
Decision Model
I start with the non-functional requirements, because they are the engineering fundamentals every other decision rests on:
- How many users will the system support?
- How many customers will use it?
- Does it need to run 24/7?
- Which cloud providers are we going to use?
Then I sketch the system with the C4 model: the context the system lives in, the containers it's made of, and the components inside them. It's the quickest way I know to see how the pieces interact, where third-party integrations enter, and where a boundary would actually carry weight.
With both in hand, I place the boundaries using ownership, volatility, data authority, latency, failure isolation, and deployment independence as the decision axes.
Practical Defaults
Design for the requirements you have, not the ones you might have. If the system serves 10,000 users today and a five-year projection says 100,000, don't build the structure for 100,000 now. That's YAGNI: you aren't gonna need it, and if you do, you'll know far more about the problem by then.
Start with fewer boundaries, make contracts explicit, and extract a service only when ownership or volatility justifies the operational cost.
Make the agreements visible. If the boundaries, contracts, and owners live in one person's head, the design drifts the first time that person is busy.
Follow the Boy Scout rule. In a legacy system or ecosystem, every change should leave the area a little better than it was, without breaking anything in production. When you're building a new service or backend from scratch, advocate for the same guideline from the first commit, so it becomes the team's habit before there's legacy to clean up.
Tradeoffs
Coarse boundaries reduce operational overhead but can hide domain coupling. Fine boundaries improve autonomy but increase coordination cost.
Designing for today's numbers means some parts will need rework if growth arrives. Designing for the five-year projection means paying for complexity now to serve users who may never show up, and the second cost is usually the one that hurts more.
Boy Scout improvements add a little scope to every change. Keep them small and safe, and they add up without putting production at risk.
Example Workflow
- Write down the non-functional requirements: users, customers, availability, and cloud providers.
- Sketch the C4 context and container views to see how the components and third-party integrations interact.
- Map the business capability, name the owner, and identify the source of truth.
- Define the consumer contract, including the contracts with third parties, and write contract tests.
- Choose the simplest deployment shape that meets today's requirements.
- Record the agreements where the whole team can see them.
- On every later change, leave the area a little better without breaking production.
Checklist
- The non-functional requirements are written down.
- A C4 context and container view exists.
- The design is sized for current requirements, not a projection.
- The owner of the behavior is explicit.
- The source of truth is explicit.
- Consumers can use the contract without internal knowledge.
- Failure behavior is documented.
- Tests protect the public contract.
- The agreements are visible to the whole team.
Related Playbooks
- AI-assisted backend engineering without losing architectural rigor
- How I make architecture decisions without pretending certainty
Bring This To Your Team
This topic works well as a workshop for teams splitting monoliths, integrating third-party systems, starting a new backend from scratch, or cleaning up ownership in a legacy one.