Microservices

Service Ownership Boundaries in Microservices

Service ownership boundaries decide whether microservices scale your org or stall it. Draw them by data ownership and team, not by code size. The rules.

Part of Microservice Service Design: Boundaries That Hold
Service ownership boundaries, shown as clearly demarcated glowing zones with one amber owned zone

Service ownership boundaries are the most consequential decision in a microservice system, and the most commonly botched. Draw them around data ownership and a single accountable team, not around code size or technical layers. Get this right and services deploy independently; get it wrong and you have a distributed monolith with all the cost of microservices and none of the benefit.

The symptom of bad boundaries is unmistakable: every feature requires a coordinated deploy across three services, and no one is sure who owns the bug. The cause is almost always boundaries drawn by technical convenience instead of by ownership.

Why service ownership boundaries matter

Microservices are an organizational technology before they are a technical one. The whole point is letting independent teams ship independently. That only works if each service has a clear owner and a clear edge.

When boundaries are wrong, you pay twice: the operational cost of running many services, plus the coordination cost of a monolith, because everything is still entangled. You get the worst of both architectures. This post is part of the Service design series and pairs with Bounded Contexts in Real Microservice Systems.

How do you define service ownership boundaries in microservices?

Draw the boundary around data ownership and a single owning team. A service should own its data exclusively, expose that data only through its API, and have exactly one team accountable for it end to end, from writing the code to carrying the pager. Code size and technical layers are the wrong axes.

The test is ownership, not lines of code. Ask three questions of any candidate service: What data does it exclusively own? Which single team is accountable for it in production? Can it deploy without a coordinated release with its neighbors? If the answers are clean, the boundary is good.

Boundaries drawn by technical layer (“the database service,” “the validation service”) fail this test immediately, because no business capability lives in one of them. A feature crosses all of them, so every feature needs every team.

Should two services share a database?

No. A shared database couples services through its schema: one team’s migration can break another team’s service, and neither can deploy independently. Each service must own its data store and expose data only through its API, so internal schema changes stay invisible to everyone else.

The shared database is the single most common way teams accidentally build a distributed monolith. It looks efficient (why run two databases?) and it quietly destroys the independence that justified splitting the services at all.

When you discover a shared database, the migration path is to assign each table to exactly one owning service, route all other access through that owner’s API, and split the physical store last. The ownership change matters more than the physical split.

Should one team own multiple microservices?

Yes, and it is healthy. A single team comfortably owns several related services within its domain. The arrangement that fails is the inverse: many teams sharing ownership of one service, where accountability blurs and every change needs cross-team negotiation.

This follows Conway’s Law, which observes that systems mirror the communication structure of the organizations that build them. If you want services to be independent, the teams must be independent, and the cleanest mapping is one team owning a coherent set of services that together deliver a business capability.

A useful inversion of Conway’s Law is the deliberate one, often called the Inverse Conway Maneuver: design the team boundaries you want first, then let the service boundaries follow. The org chart and the architecture are the same diagram.

This matters more than it sounds, because Conway’s Law is not advice you can decline. It is an observation about what will happen. If four teams build a system, you get a system with four-ish major seams whether or not anyone designed them, and those seams land wherever communication was hardest. The only real choice is whether you place them deliberately or discover them later in the shape of your incidents.

The practical consequence is that a boundary redraw is usually a team change wearing a technical disguise. Moving a service from one team to another, without changing a line of code, frequently fixes more than a refactor would, because it aligns the change reason with the accountability. Conversely, a beautifully factored boundary that cuts across two teams will erode, because every change now requires a meeting, and engineers route around meetings.

So when a boundary keeps drifting back to the wrong shape despite repeated cleanup, stop treating it as an architecture problem. Ask who has to talk to whom to ship a typical change. The architecture is describing your org chart accurately, and the org chart is the thing to edit.

What is the biggest sign of a bad service boundary?

The clearest signal is a change that routinely requires coordinated deploys across multiple services. If shipping one feature reliably touches three services and three teams every time, the boundary is in the wrong place. Healthy boundaries let most features ship within a single service.

Watch for these boundary smells:

  • Lockstep deploys. Service A cannot release without B and C releasing together.
  • Chatty synchronous call chains. One request fans out into a deep tree of internal calls, each a failure point.
  • Shared data stores. Two services reading the same tables.
  • Ownership disputes. A bug arrives and two teams each think it is the other’s.
  • A “god” service. One service everything depends on, that no one can change safely.

Each of these means a boundary is in the wrong spot. The fix is rarely more services; it is usually moving the line so that what changes together lives together.

Smells are only useful if they point at a specific remedy, so here is the diagnosis table I actually use in review:

SmellWhat it actually meansThe fix
Lockstep deploysThe services share a change reason; the boundary splits one capabilityMerge them, or move the shared behavior wholly into one owner
Chatty synchronous chainsData that is read together is stored apartCo-locate the data under one owner, or denormalize a read model
Shared data storeThere is one service wearing two deployablesAssign every table one owner; route others through its API
Ownership disputesNo team is accountable, so both are optionalName one owning team in a registry, including on-call
A “god” serviceA capability grew until no team can hold itSplit along the next data boundary, not at a file count
Frequent cross-team PRsConway’s Law is fighting youRedraw team boundaries first; services follow

How do you make ownership explicit rather than assumed?

The failure mode behind “ownership disputes” is that ownership usually lives in tribal knowledge. Someone knows who owns the billing service. That knowledge does not survive a reorg, a departure, or a 3 a.m. page routed to the wrong rotation.

The durable fix is to make ownership a declared, machine-readable fact rather than a convention, and to put it next to the thing being owned instead of in a wiki that drifts.

Concretely, that means every service and every shared resource names its owner in a file that lives in the repository and is checked in CI. In the architecture I run, the event bus makes this explicit: a single canonical topic registry is the source of truth, and every topic declares its owning service and the consumers permitted to read it. Topics are never created ad hoc against the cluster; they are applied from that registry by a script. Ownership is therefore not a claim in a document — it is a property of the deployed system, and a change of owner is a reviewable diff.

That one discipline removes an entire class of argument. When an event’s shape needs to change, there is no debate about who decides: the registry names the owner, and it names precisely which consumers must be considered. It also converts the vaguest boundary question — “who owns this data?” — into something a script can answer.

The same idea generalizes beyond Kafka. A CODEOWNERS file, a service catalog entry with an on-call rotation attached, and a data-store-to-owner mapping all do the same job: they make the boundary legible to someone who was not in the room when it was drawn. For the naming and governance rules that make a registry like this workable at scale, see Kafka Topic Naming and Governance.

How do you split a service that has grown too big?

Knowing a boundary is wrong is easier than moving it while the system serves traffic. The sequence that works is ownership first, physical separation last — the reverse of most instincts.

  1. Find the seam by change reason. Look at the last six months of commits and group them by why they happened. Two clusters of changes that never appear in the same commit are two capabilities.
  2. Assign every table one owner. Do this on paper before any code moves. If a table cannot be assigned, the seam is still wrong.
  3. Route foreign access through an API. The other capability stops touching those tables directly and calls the owner instead — while both still live in the same deployable. This is the step that actually decouples, and it is reversible.
  4. Let it run. Live with the internal API for a few weeks. Every awkwardness you feel now is a bug you would otherwise ship into a network hop.
  5. Split the deployable. Only now does the second service become its own process, with its own store.

The reason this order works is that steps 3 and 4 are cheap to undo and step 5 is expensive. Teams that split the deployable first discover the seam was wrong only after they have a network partition, a distributed transaction, and two on-call rotations standing between them and the fix.

When is shared ownership actually acceptable?

The rule “one service, one owner” is right often enough to be a default, but stating it as an absolute law is dishonest, and engineers stop trusting rules that never admit exceptions.

Shared ownership is workable in three cases. Platform libraries and internal tooling genuinely benefit from many contributors, because the consumers are the experts; the mitigation is that one team still owns the release and the roadmap even when everyone can send a patch. A service in active handover has two owners by definition, and the honest move is to timebox it and name the date ownership transfers rather than let it drift. Very small organizations where one team owns everything have no meaningful boundary to violate — the discipline matters when the team splits, so the useful work there is drawing the data boundaries early even while a single team owns both sides.

What is never acceptable is ambient co-ownership: two teams both changing a service, neither on its pager, each assuming the other is watching. That is not shared ownership, it is unowned code with two sets of contributors, and it fails at exactly the moment it matters.

The test for any exception is the pager. Whoever gets woken up owns it. If the answer to “who gets paged” is “it depends,” the boundary is broken regardless of what the org chart says.

How big should a microservice be?

Big enough to own a complete business capability and its data, small enough that one team can own it fully. There is no line count. “Micro” refers to a focused scope of responsibility, not a tiny codebase, and chasing smallness for its own sake is how teams end up with a sprawl of chatty nano-services.

The right sizing question is not “how many lines” but “does this own one coherent thing.” A service that owns the entire billing capability might be substantial and still correctly sized, because billing is one cohesive responsibility with one team. Conversely, ten 200-line services that must always deploy together are mis-sized, no matter how small each one looks.

Use the team and the capability as your ruler. If a single team can own the service end to end, and the service maps to a capability a business person would recognize, the size is right. When a service grows until no one team can hold it, or until two distinct capabilities live inside it, that is the signal to split, and the split should fall along the next ownership and data boundary, not at an arbitrary file count.

The cohesion-and-coupling rule

The durable principle underneath all of this is simple: maximize cohesion inside a service, minimize coupling between services. Things that change together belong in the same service; things that change independently belong in different ones.

This is why data ownership is the right boundary axis. Data that is read and written together as part of one business capability is cohesive, and putting it behind one owner minimizes the coupling everyone else has to it. A boundary that splits cohesive data, or couples independent data, is fighting the grain of the system.

A concrete example makes the rule tangible. Suppose “place order” and “calculate order total” always change together and always touch the same order data; splitting them into two services just means every order change is a two-service deploy with a network hop in the middle. They are cohesive, so they belong together. By contrast, “place order” and “send marketing email” change for completely different reasons on completely different schedules; coupling them in one service means a marketing tweak risks the checkout path. They are independent, so they belong apart. The grain of the system is visible if you ask, for any two pieces of behavior, whether they change together or change independently.

A worked example: one bad boundary, redrawn

Abstract rules are easy to agree with and hard to apply, so here is the shape of a boundary error I have seen repeatedly, and what moving it looks like.

A team has three services: user-service, profile-service, and settings-service. It looks like clean separation — three nouns, three deployables. In practice every feature touches all three, they share a users database, and a release train ships them together on Thursdays.

Run the three ownership questions against it. What data does each exclusively own? Nothing: all three read and write users and its satellite tables. Which single team is accountable? One team owns all three, which is fine, but it means the split bought no organizational independence. Can each deploy alone? No — the Thursday train exists precisely because they cannot.

So the boundary is drawn on nouns, not on change reasons. A user’s display name, their notification preferences, and their authentication credentials are all “user data,” but they change for entirely different reasons, on different schedules, with different risk profiles. Meanwhile display name and avatar always change together and were split apart.

The redraw groups by change reason instead. Identity — credentials, sessions, account lifecycle — is high-risk, changes rarely, and needs the strictest review. Profile — display name, avatar, bio — changes constantly and is low-risk. Preferences — notification settings, locale — changes on its own schedule and is mostly read by other systems. That is two or three services with genuinely different data, different deploy cadences, and different blast radii, and now most features touch exactly one.

Note what did not drive the decision: none of the three original services was too big, and the redraw might even reduce the service count. Sizing was never the problem. The problem was that the lines fell across capabilities instead of between them.

The tell is worth memorizing, because it generalizes: if your service names are nouns from your data model rather than capabilities from your business, you probably split a schema instead of a system.

A service-boundary checklist

Before you carve out or merge a service, confirm:

  • The service owns a clear set of data, and nothing else reads or writes it directly.
  • Exactly one team is accountable for it, including on-call.
  • It can deploy independently, without a coordinated release.
  • A typical feature in its domain ships within this one service.
  • Its API is the only way other services get its data.
  • Removing it would break a nameable business capability, not just “a layer.”

What I’d do differently

The mistake I see most is splitting services too early and along technical lines, because microservices feel like the goal. They are not the goal. Independent deployability by independent teams is the goal, and a well-factored modular monolith often delivers more of that than a premature mesh of chatty services.

If I were drawing boundaries again, I would start from the team and the data: which team owns which business capability, and what data does that capability own. The services fall out of that almost mechanically. Draw the org and the data first, and the service boundaries stop being guesses. The conceptual tool for finding those data boundaries is the bounded context, covered next in Bounded Contexts in Real Microservice Systems.

Sources

Frequently asked questions

How do you define service ownership boundaries in microservices?

Draw boundaries around data ownership and a single owning team, not around code size or technical layers. A service should own its data, expose it only through an API, and have exactly one team accountable for it end to end, from code to on-call.

Should one team own multiple microservices?

Yes, that is normal and healthy. One team can own several services. What breaks down is the reverse, multiple teams sharing ownership of one service, because no one is clearly accountable and changes require constant cross-team coordination.

What is the biggest sign of a bad service boundary?

A change that routinely requires coordinated deploys across several services. If a single feature touches three services every time, the boundary is in the wrong place and you have a distributed monolith, not microservices.

Should two services share a database?

No. A shared database couples services through the schema and destroys independent deployability. Each service owns its own data store and exposes data only through its API, so the schema can change without breaking other services.

How big should a microservice be?

Big enough to own a complete business capability and its data, small enough that one team can own it end to end. There is no correct line count. "Micro" describes a focused scope of responsibility, not a small codebase, and chasing smallness produces chatty nano-services.

How do you split a microservice that has grown too large?

Ownership first, physical separation last. Find the seam by grouping recent commits by change reason, assign every table exactly one owner, route foreign access through an API while both halves still share a deployable, live with it for a few weeks, and only then split into separate processes.

Is shared ownership of a service ever acceptable?

Only in narrow cases: platform libraries with many contributors but one team owning releases, a service in a timeboxed handover, or an org small enough that one team owns everything. Ambient co-ownership, where neither team carries the pager, always fails. The test is who gets woken up.

How do you make service ownership explicit?

Declare it in a machine-readable file next to the thing being owned and check it in CI, rather than relying on tribal knowledge. Service catalogs with on-call rotations, CODEOWNERS, and event-topic registries that name an owning service all make the boundary survive reorgs and departures.