← All field notes

From a Monolith to Services: Decide What Actually Needs to Move

A monolith is not automatically a problem, and services are not automatically an upgrade. Move only the boundaries that solve a problem you can name and measure.

Engineering

Teams rarely wake up wanting microservices for their own sake. They want releases to stop stepping on each other, one overloaded area to scale independently, or a fragile application to become easier to change. “Break up the monolith” becomes shorthand for all of those frustrations, even when the shape of the code is not the main cause.

Before drawing service boxes, get specific about the pain. Slow builds, unclear ownership, a shared database, and unpredictable requirements call for different fixes. Some can be solved inside the existing application for a fraction of the disruption.

Make the monolith honest first

If nobody can describe the current modules, data flows, or deployment process, a distributed version will only spread the confusion across a network. Map the high-change areas, the jobs that fail most often, and the tables or integrations that everything depends on. Add enough tests and observability to know when behavior changes.

A modular monolith is often the best intermediate state. Clear internal boundaries, explicit interfaces, and fewer surprise dependencies create most of the organizational benefit without adding remote calls and multiple deployments.

Look for a boundary with a reason to exist

Good first candidates usually have a distinct workload or ownership model. Image processing may need very different scaling from account administration. A reporting pipeline may be able to fail without taking checkout down. A capability maintained by a separate team may benefit from its own release schedule.

Avoid carving out whatever looks easiest if it has no operational reason to be separate. A tiny service that changes every time the monolith changes is not independent; it is an extra place for the same change to fail.

Treat data ownership as the hard part

Moving code behind an API is easy compared with deciding who owns the data. Two services writing the same tables remain one system with a network in the middle. Give the extracted capability a clear source of truth and decide how other parts of the product learn about changes.

That decision brings tradeoffs: duplicated read data, delayed consistency, retries, and reconciliation. Write down which delays are acceptable and which operations must remain atomic. “Eventual consistency” is not a design until the team knows what users see while they wait.

Plan for failure between the boxes

A method call inside one process either returns or throws. A remote call can time out after the other side completed the work. Retries can create duplicates. Queues can back up. Build idempotency, timeouts, circuit breakers, and useful correlation IDs into the first extraction instead of adding them after an incident.

The operational work also includes deployment, secrets, logs, alerts, local development, and on-call ownership. If the team cannot support that extra surface, it is not ready to multiply it.

Extract one thing and pause

Choose a boundary with an observable benefit, route a small percentage of work through it, and keep a way back. Measure whether releases, reliability, or cost actually improved. Include the time spent operating the new service in that review.

The result may justify another extraction. It may also show that the remaining monolith is stable and understandable. That is a perfectly good stopping point. Architecture should make the business easier to operate, not satisfy a diagram.