Shipmind Labs

Most monolith-to-microservices migrations we inherit are already half-done: the code is split into services, and every one of them still writes to the same database.

That is the version that hurts the most.

The team gets all the costs of distribution — network calls, deploy coordination, partial failures, harder debugging — and none of the independence they were promised. Two services still can't be deployed separately, because a column change breaks both. A migration still locks the whole product. Nobody owns the schema, so everybody edits it.

When we take over rewrites of inherited systems, we work in the opposite order. Data boundaries first, code second.

That means picking one domain that genuinely has its own lifecycle — payments, documents, notifications — and asking a boring question: can this domain hold its own tables and expose everything else through an API? If the answer requires a join across three other domains, it isn't a service yet. It's a module, and it should stay in the monolith until the data separates cleanly.

We've pulled service layers out of monoliths this way on payment platforms, lending systems and marketplaces. The pattern holds: the first extraction is slow and unglamorous, because it's mostly about untangling reads. Everything after it goes faster.

Our take: a monolith with clean internal boundaries is a better business than a distributed system with a shared database. Split when the data is ready, not when the org chart is.

For teams that went through a rewrite — what was the first piece you extracted, and would you pick the same one again?

Was this useful?

Building something similar?

or email hello@shipmindlabs.com