All articles
12 September 2026
9 min read

Which service to pull out of the monolith first

By Tim Rutte, Cloud & Software ArchitectTopicBackend & Platforms

A Jenga tower with two gaps, still standing; the blue block that was pulled out lies in front of it.

When a team decides to pull things out of a grown monolith one at a time, the first question is almost always the wrong one. It is: how do we cut? Domain boundaries, bounded contexts, there is a lot of good literature about it.

The question that has to be answered first is a different and far more practical one: which part comes out first? Because the first cut decides whether there will be a second. If it goes well, the project is justified and the team has learned the procedure. If it goes wrong, the next meeting records that it was tried and that it does not work.

This article describes how I recognize the first candidate, which three parts almost always fit and which three you never touch first. What the integration looks like technically afterwards I have written up elsewhere; this is about the choice before that.

Four criteria for the first candidate

A good first candidate meets four conditions. None of them is about clean domain modelling, and that is deliberate: the first time round is about the procedure, not the architecture.

One: it carries load you can see. The part noticeably consumes compute, memory or waiting time. That makes success measurable without anybody having to believe in an improvement, and the benefit appears in the same meeting as the cost.

Two: the boundary is narrow. Few places in the monolith call this part, and it calls back in few places. The narrower the boundary, the smaller the interface you have to invent, and the less opportunity to invent it wrongly.

Three: it shares little state. The part reads two or three tables and writes to one. If instead it reads across the whole schema, it is not a service but a view onto the monolith, and pulling it out only moves the problem onto the network.

Four: a failure hurts, but not revenue. The first service will have bugs, and that is part of it. A part whose failure means a delay is suitable. A part whose failure prevents an order is not.

Where the four criteria conflict, the second one wins. A narrow boundary rescues a mediocre candidate; a wide one ruins the best.

Measuring the boundary instead of estimating it

Whether a boundary is narrow cannot be settled in conversation. Two measurements are enough, and both take an hour.

Who calls the candidate? In a PHP monolith that is a search across the entry points of the classes belonging to the candidate:

# Every place using classes from the candidate's namespace
grep -rn "App\\ImageProcessing\\" src/ --include="*.php" \
  | grep -v "^src/ImageProcessing/" \
  | cut -d: -f1 | sort -u

Fifteen files is a narrow boundary. Two hundred is not, and then the answer is not "do it anyway" but look for a different candidate.

Which tables does it read and write? The most reliable source for that is not the code but the query log. One hour of production traffic with the log on, filtered to the requests that pass through the candidate, produces an honest list. The code produces an incomplete one, because it does not know about queries somebody assembles at runtime.

The second measurement is the one that saves projects. A part that looks nicely bounded on paper and in reality reads fourteen tables, four of them for writing, is not a first candidate.

Three candidates that almost always fit

In the systems I have seen it was almost always the same three corners, and for the same reasons.

Image or file processing. Upload, convert, resize, put into storage. It is compute-heavy, has a narrow boundary (in: a file, out: a path), shares almost no state, and a failure means a delay. On top of that comes a practical advantage: inside the monolith that work ties up worker processes that are then missing for requests.

Exports and reports. A job that reads a lot of data, runs long and produces a result. The boundary is narrow, because there is usually one call, and the benefit is immediately visible, because such runs regularly slow everything else down inside the monolith. Watch the third criterion: a report reads broadly by nature. That is fine here, as long as it only reads.

Search and feed interfaces. Anything that takes data from the monolith, prepares it and serves it in a form of its own. The state is derived and can therefore be rebuilt at any time, which makes the way back easy: if it does not work, switch back and throw the index away.

What all three have in common is that they sit at the edge. The first service is not cut out of the middle.

Three places you never touch first

Just as reliable are the places where a first cut goes wrong. All three are tempting, because they look cleanly bounded in domain terms.

Login and permissions. In domain terms that looks like a service of its own. In practice every single request depends on it, the state is shared (sessions), and one bug locks out every user at once. On top of that: the benefit is invisible, because nothing improves for anybody.

Orders, payment, contracts. The core of the business is the part with the most special cases, the most dependencies and the least tolerance for mistakes. It gets cut eventually, but not first, and not before the team has learned the procedure on something harmless.

Master data. Customers, products, prices. Everything reads it, a lot writes it, and it is shared state par excellence. A master data service is a sensible destination and a catastrophic start, because every delay and every inconsistency becomes visible everywhere at once.

What the seam between the old and the new part looks like, without the old vocabulary coming along, is covered in Building an anti-corruption layer between Go and PHP.

The cut: the data stays where it is, for now

The most common overreach with a first service is giving it its own database immediately. That is the right long-term goal and the wrong first step, because it puts two hard things into one move: a new service and a data migration.

For the first cut it is entirely acceptable for the new service to read the same database as the monolith. That is not a clean end state, and it has one decisive advantage: the way back stays trivial. If the service turns out not to work, you switch the call in the monolith back, and there is nothing to migrate in reverse.

Two rules keep that intermediate state clean, and both belong in the written decision.

The new service does not write to tables the monolith also writes to. Reading: yes. Shared writing creates cases nobody can reason about any more, the moment two systems hold different assumptions about locking.

The intermediate state gets an end date. Not because it is bad, but because otherwise it becomes permanent, and in two years the shared database is the reason nobody can change anything.

Towards the front the service is addressed through a thin layer that keeps the old data model out of the new code. The pattern is called anti-corruption layer, and it is the difference between a new service and a second piece of legacy.

What happens when the cuts follow the nouns rather than the transaction boundaries is covered in The distributed monolith.

How you notice you cut in the wrong place

Four signs, and every one of them is a reason to roll back rather than push on. The way back is cheap at this point; it will never be cheap again.

Every change needs both sides. If for weeks no change has been possible in the service alone or the monolith alone, the boundary is in the wrong place. Instead of a separation you have built an additional connection.

The interface grows. Three calls have become twelve, and the new ones are named the way internal methods are named. That is the sign that the monolith is using the service as an extension of itself.

Response time got worse. A call that used to be a method call is now a network call. If it sits inside a loop, a millisecond becomes a second. That is not an argument against services, it is an argument against this cut.

Nobody can say who owns a failure. If the first question at every incident is whether it is the service or the monolith, either observability is missing or responsibility is not separated. Both are fixable, and both belong fixed before the second service appears.

What comes after the first service

When the first cut holds, the most valuable thing about it is not the service. It is the things that appeared alongside and did not exist before: a way to ship two systems together, a way to switch a call back, a place where you can see what happens between them.

That is why the first candidate is chosen for learnability rather than importance. The second cut costs a fraction of the first, and the third gets boring. That is exactly when it may go into the middle.

And if the measurements show there is no narrow candidate, that is a result too. Then the next step is not a service but order inside the monolith: modules with boundaries people can keep. It is unspectacular, it is cheaper, and it is the precondition for a later cut being possible at all.

How I set up Go services next to an existing PHP system is on its own page, including the question of when that is the wrong answer.

This article belongs to a series about systems that already exist. The retrospective orders every article in it by situation.