microservice.guru
← All posts

How to Find Your Microservice Boundaries

By Alain Cyr, Founder, Koesion

microservice.guru — How to find your microservice boundaries: four services wired to each other, versus three independent capabilities.

Some history

In 2018 I joined a platform where the architecture had been drawn in a single afternoon. Five boxes on a whiteboard: User Service, Product Service, Order Service, Payment Service, Notification Service. Everybody nodded. It looked exactly like the microservice pictures we had all seen in conference slides.

Six months later, the same team could not ship a simple story — “apply a promo code at checkout” — in one sprint. The story needed a change in four repositories, four pull requests, four reviews, and four deployments in a precise order. If one of them failed, all four went back. Standup had become a scheduling meeting.

Nobody had done anything wrong, technically. The code was clean. The pipelines were green. The boundaries were wrong.

This is the post I wish I could have given that team. Everything else I write about microservices — contracts, events, testing, documentation — sits on top of this one decision.

The noun trap

Where do names like “User Service” come from? Almost always from the database diagram.

You open the entity-relationship diagram, you draw a circle around each table, you add the word Service at the end, and you have an architecture. It feels safe. Everybody in the room understands what a user is, what an order is, what a product is. No argument, no workshop, no ugly conversation with the business.

The problem is simple: a noun is a data structure, not a behaviour. A microservice is not a table with HTTP in front of it. A microservice is a piece of the business that can do something end to end, by itself.

When you split by noun, you have not decomposed the system. You have distributed the tables and kept the logic between them, in the network.

The failure mode you will recognize

Here is what a noun-split architecture looks like after two or three quarters. Check the ones you recognize:

01 — one story fanning into 4 repos, 4 ordered deploys, shared schema

The distributed monolith: four repos, four deploys, one unit of change.

This is the distributed monolith. You paid the full price of distribution — network calls, retries, timeouts, tracing, four pipelines, four on-call rotations — and you got none of the benefit. A monolith would have shipped that promo code story in two days.

The tell is always the same: the unit of change is bigger than the unit of deployment.

What a boundary is actually made of

Two ideas do the work here. They are not new, and they are not academic. They come from Domain-Driven Design, and they are the most practical thing in it.

1. Business capability — what the company does

A business capability is something the business does, and would still do with paper and a telephone: take an order, decide a price, screen for fraud, get the parcel to the door, collect the money.

Notice that these are verbs, not tables. Capabilities are stable — a retailer has been taking orders and shipping parcels for a hundred years. Org charts change, database schemas change, capabilities almost never do. That is exactly what you want a boundary built on.

Ask the question this way: if I removed this service, which part of the business would stop working? If the honest answer is “none, but three other services would break”, you are looking at a table, not a capability.

2. Bounded context — where a word means exactly one thing

A bounded context is the area where a word has one clear meaning, and where the model behind that word is consistent. Walk from the sales floor to the billing department to the support desk and ask each of them what a customer is. You will get three different answers, and all three are correct.

02 — "Customer" meaning three different things in Sales / Billing / Support

The same word does not survive a walk across the company.

Three models, three vocabularies, three life cycles, three owners. That is three boundaries — not one “Customer Service” trying to be all of them at the same time and pleasing nobody.

So: the capability tells you what a service is for. The bounded context tells you where it stops.

So how big is one? A microservice is a subsystem

Micro is the most expensive word in our industry. It has pushed a whole generation of teams to build things that are far too small, and then to spend their nights holding the pieces together.

In my trainings I say it in one sentence, and I repeat it until it sticks: a microservice is a subsystem. Something substantial enough that a team can own it, run it in production, and discuss it with the business using the business's own words. Not a class. Not a table. Not an endpoint. A subsystem.

So here is the counting rule I give people. If you have 25 microservices, one of two things is true:

In practice it is almost always the second one. A typical digital platform has five to ten subsystems in it, not twenty-five. I have never seen a product organisation of forty people that needed more than a dozen services — but I have seen plenty that had built forty.

Count your teams before you count your services. A team can comfortably own two or three subsystems. A subsystem cannot be owned by half a person. If you cannot say out loud, today, the name of the team that owns each service, you have too many services. That is the whole test.

And remember what you are actually buying when you split. Cutting a service in two does not remove complexity — it moves that complexity into the network, where you can no longer see it, step through it, or test it on your laptop. Nobody ever promised you five-hundred-line services. Micro was never about lines of code.

And a microservice is not a reusable SOA service!

This one I have to correct in every single training, and usually with the most experienced people in the room — the ones who lived through SOA and are pattern-matching on what they already know.

The two ideas ask opposite questions.

You cannot maximize both. Every extra consumer of your service is one more team you are not allowed to break, one more meeting before a schema change, one more version you have to keep alive. Reuse buys you a little less code and costs you your independence — which was the only thing you were trying to buy in the first place.

So a shared Customer Service called by nine applications is not a microservice. It is an ESB endpoint with a container around it, and it will become the bottleneck of your platform: exactly the bottleneck we spent the 2000s trying to escape.

The test takes five seconds. Ask why the service exists. If the answer is “so that the others can reuse it”, it is not a microservice. If the answer is “because somebody has to own pricing”, it is.

How to find them, concretely

This is the part people ask for. It is not an algorithm, but it is a repeatable process, and it takes days, not months.

Step 1 — Get the events on a wall. Run an event storming session with the business, the product owner, the analysts and the developers in the same room. Only one rule to start: write down everything the business notices, in the past tense. Order placed. Payment authorised. Card declined. Stock reserved. Parcel shipped. Refund issued. No nouns yet. No tables. No services.

Step 2 — Cluster the events. Events that always happen together, triggered by the same people, in the same part of the process, belong together. Put a loose circle around each group.

Step 3 — Listen for the language changing. This is the strongest signal you will get, and it is free. Somewhere on that wall, people stop saying “cart” and start saying “shipment”. They stop saying “customer” and start saying “payer”. A change of vocabulary is a boundary. Draw the line exactly there.

Step 4 — Check the owner. One context, one team. If two teams have to argue about one context, the context is too big. If one team owns six of them, you have made too many. Conway's law is not a warning, it is a design tool — use it on purpose.

Step 5 — Name it and document it. Name the service after the capability, with words the business would recognize: Ordering, Fulfilment, Pricing, Payments. Then write it down — I use the microservice canvas, kept next to the code.

03 — event storm → clusters → named, owned contexts

Events, then clusters, then a name and an owner.

Five questions to test a candidate boundary

Before you create the repository, put your candidate service through these. They take five minutes and they have saved me quarters of work.

  1. Can a typical story be finished inside it? Take the last ten stories from the backlog and place each one. If most of them fit in one service, the boundary is good. This is the single most useful test I know.
  2. Can it be deployed alone, on a Friday afternoon? If the answer requires the words “as long as”, it is not a boundary.
  3. Does it own its data? Is it the only writer of that data? If two services write the same table, they are one service that happens to be running twice.
  4. Can it answer a request without a synchronous call to another service in the critical path? One or two is life. Four in a chain is a monolith with extra latency.
  5. Would somebody from the business recognize the name? If you have to explain the name of a service to the product owner, the service was designed for the database, not for the business.
04 — the same promo-code story, split by noun vs. by capability

The story did not get smaller. The boundary moved so that the story fits inside one service.

“But everybody needs the customer data!”

This is the first objection, every time. And it is where teams give up and rebuild the shared database.

The answer is that duplication is not a bug here, it is the design. Fulfilment does not need your Customer entity. It needs a name and a delivery address, captured at the moment the order was placed — and frozen, because you do not want yesterday's parcel to move when somebody edits their profile today.

So the events carry the copies. Each service keeps the small slice it needs to do its job, and one service — exactly one — is declared the source of truth for each piece of data. Write that down in the documentation. The rest is a local, disposable copy.

You are not denormalizing by accident. You are choosing autonomy over a single shiny record.

Do's

Dont's

Big warning!

You will get the boundaries wrong the first time. Everybody does. On day one, nobody in the room knows the domain well enough — not the architect, not even the business.

The real risk is not being wrong. The real risk is being wrong and unable to move. So keep them big at the beginning. A service that is too large is easy to split later; four services that are wrong are extremely hard to merge, because by then four teams, four pipelines and four backlogs depend on them.

So when you hesitate between two designs, take the one with fewer and bigger services. Start with a modular monolith, or with two or three coarse services drawn on capability lines. Let the domain teach you where it wants to break. When one part of the code starts speaking a different language than the rest, that is the domain telling you that a boundary is ready — and only then extract it.

And expect the map to change. A boundary that was right in 2020 can be wrong in 2024 because the business changed. That is not architectural debt. That is the business being alive.

References