SaaS technology
Businesses SaaS July 24, 2026 • 11 min read

Monolith vs. Microservices: Pick the Right Cut Point

For: A CTO or VP Engineering at a 3–5 year old B2B SaaS company whose monolith is slowing down deployments and whose team is debating a microservices migration — not because the system is broken, but because two teams keep stepping on each other and deploy velocity has halved in the last six months

If two teams are stepping on each other and your deploy velocity has halved, the honest answer is almost always: fix your module boundaries inside the monolith first, and only extract services where operational independence is the real bottleneck. Microservices solve operational independence — who ships what, when — at the direct cost of cognitive independence, which is the problem most struggling teams actually have. Get that diagnosis wrong and you spend a year of engineering capacity trading one kind of pain for a worse one.

This post gives you a framework to make that call cleanly.

Define the decision crisply

The question is not "monolith or microservices." That framing has cost more engineering years than any other architectural debate. The actual question is:

Which specific seams in your current system, if made into independently deployable services, would remove more friction than they create?

The answer is almost never "all of them" and almost never "none." It's usually two or three. And you can only find them by naming the friction precisely.

The two kinds of independence — and why teams confuse them

Every team that says "we need microservices" is really saying one of two things:

Microservices are excellent at solving the first. They actively make the second worse, because now the coupling is hidden behind network calls, version skew, and eventually-consistent data instead of being visible in the type system and the compiler.

If your pain is cognitive — and for most 3–5 year old B2B SaaS monoliths, it is — extracting services distributes the confusion instead of resolving it. You still don't understand the system. You just can't grep it anymore.

The five axes that actually matter

Score your situation honestly on each. Be specific — write down the actual incident, PR, or Slack thread that proves the score.

1. Deploy contention (operational)

How often does one team's release block or break another team's? If your answer is "every deploy is a coordination meeting" or "we batch releases weekly because independent deploys are risky," this is a real operational-independence problem. If your answer is "deploys are fine, but the codebase is scary," this axis is a zero.

2. Runtime coupling vs. code coupling

Runtime coupling means Module A calls Module B at request time and can't function without it. Code coupling means they share types, utilities, or a database schema. Microservices only help with code coupling. They convert runtime coupling from a function call into a network call — usually a downgrade. Map your top 10 friction points and label each. If most are code coupling, that's a modularization problem, not a distribution problem.

3. Team topology and ownership clarity

Can you draw a line on the codebase and say "this team owns this, that team owns that" without argument? If no, extracting services will not create ownership — it will freeze the ambiguity in place, but now with API contracts and on-call rotations attached to the confusion. Fix ownership in the monolith first. It's cheap and reversible.

4. Data boundaries

Do your bounded contexts have clean data boundaries, or does everything join against a central users and accounts table? Distributed data is the single hardest part of a microservices migration. If you can't identify three or four aggregates that own their data cleanly today, you are not ready to extract services — you're ready to refactor your schema.

5. Scaling shape

Do different parts of your system have wildly different load profiles? A background job worker that needs 10x the CPU of the API. A search endpoint that needs different memory. A webhook processor that has spiky traffic. If yes, this is a genuine reason to extract — not for team velocity, but because you're paying for the peak of every component. If your load is roughly uniform, this axis is a zero.

Scoring the three real options

You don't have two options. You have three. Most teams skip the middle one and go straight from monolith to microservices, which is where the regret lives.

Option A: Stay monolithic, enforce hard module boundaries

What it looks like: Modular monolith. Enforced package boundaries (using tooling like ArchUnit for Java, dependency-cruiser for Node, or module-level linting). Separate database schemas per module, no cross-schema joins. Clear code ownership via CODEOWNERS. Trunk-based development with feature flags.

Good at: Preserving cognitive independence. Cheap to refactor. Deploys stay simple. You keep transactional integrity. You keep your ability to grep. Reversible.

Bad at: Doesn't solve deploy contention if that's genuinely your bottleneck. Requires cultural discipline — the boundaries exist only if the team defends them in PRs. Doesn't help with heterogeneous scaling.

Option B: Extract two or three services at the real seams

What it looks like: Identify the two or three subsystems with genuine operational independence needs — usually async workers, a heavy compute pipeline, a webhook processor, or a bounded context with a distinct scaling profile. Extract those. Leave the rest as a monolith. Accept that you now have a distributed system, with all the observability, deploy, and testing overhead that implies.

Good at: Solving specific operational bottlenecks without paying the full microservices tax. Lets teams deploy independently where it matters. Keeps the cognitive load bounded because most code stays in the monolith.

Bad at: Requires real judgment about where to cut. Wrong cut points create worse coupling than you started with. Every extracted service adds a permanent operational burden: CI, monitoring, on-call, deploy pipeline, secrets management.

Option C: Full microservices migration

What it looks like: Decompose the monolith into 8–20+ services, each with its own database, deploy pipeline, and team ownership. Service mesh or API gateway. Distributed tracing. Contract testing. Saga patterns for cross-service transactions.

Good at: Genuine team autonomy at scale (think 50+ engineers, 8+ teams). Independent scaling of every component. Polyglot stacks when there's a real reason for them.

Bad at: Everything else, at your size. You'll spend a large fraction of engineering capacity on platform work instead of product. You'll lose transactional guarantees and have to rebuild them at the application layer. Onboarding gets harder, not easier. Debugging a production issue now requires reading five services' logs. If your team is under ~30 engineers, the coordination overhead of microservices usually exceeds the coordination overhead you were trying to escape.

How to actually decide

Run this in order. Don't skip steps.

Step 1: Diagnose the friction, don't assume it

For two weeks, log every deploy delay, merge conflict, and "I didn't know that would break" incident. Tag each as operational or cognitive. If 70%+ are cognitive, microservices are the wrong tool. Full stop.

Step 2: Try the modular monolith first if you haven't

Most monoliths in pain have never had enforced boundaries. Add package-level dependency rules. Split the database into per-module schemas with no cross-schema foreign keys. Set CODEOWNERS. Give it one quarter. If deploy velocity recovers, you just saved yourself a migration.

Step 3: If you still need to extract, extract at load or async boundaries first

The safest first extractions are things that are already loosely coupled at runtime: background jobs, scheduled workers, webhook receivers, file processors, ML inference endpoints. They usually already communicate via queues, so the boundary is real. Extract those before you touch anything in the synchronous request path.

Step 4: Never extract anything that shares a transactional boundary with something else

If Module A and Module B both write to the same aggregate in the same transaction and rollback matters, they belong in the same service. Period. You can refactor the aggregate later. You cannot make distributed transactions pleasant.

If you're in situation A, do X

The migration itself, if you go

If you decide extraction is right, two rules matter more than any other:

  1. Never do a rewrite. Strangle the monolith. Route traffic gradually. Keep the monolith running for as long as it takes — often years. Every rewrite project we've seen fail did so because the team underestimated the long tail of edge cases the monolith quietly handled.
  2. Invest in platform before decomposition. You need distributed tracing, centralized logging, a deploy pipeline that works for N services, and contract testing before you extract the second service. Otherwise every new service compounds the operational debt.

If you want an outside read on where your seams actually are — versus where they feel like they are at 2am after a bad deploy — that kind of architecture review is exactly the sort of engagement our team runs as part of legacy modernization work. Not every engagement ends in a migration. Some end in "you don't need one, here's the modular monolith plan."

Frequently Asked Questions

How do I know if my monolith is really the problem?

Track two weeks of engineering friction and tag each incident as operational (deploy contention, release coordination, unrelated changes blocking yours) or cognitive (unclear blast radius, merge conflicts, onboarding pain). If cognitive dominates, microservices will make things worse. If operational dominates and is concentrated in a few subsystems, targeted extraction may help.

Can a modular monolith really scale for a B2B SaaS?

Yes, for most teams under about 30–40 engineers. Shopify, GitHub, and Stack Overflow have all run large modular monoliths well past the size most SaaS companies reach. What matters is enforced module boundaries, per-module database schemas, and clear code ownership — not the number of deploy artifacts.

What's the biggest hidden cost of a microservices migration?

Platform overhead. You need distributed tracing, centralized logs, contract testing, a deploy pipeline that scales to N services, service discovery, and secrets management — all before your second or third service is useful. Teams routinely underestimate this and end up with microservices running on monolith-era tooling, which is the worst of both worlds.

Should I rewrite the monolith while migrating?

No. Strangle it. Route traffic to new services incrementally while the monolith keeps serving the rest. Rewrites underestimate the edge cases the old system silently handles and almost always overrun. Every successful migration we've seen kept the monolith in production the entire time.

How long does a monolith-to-microservices migration take?

It depends heavily on system complexity, team size, data boundaries, and how much platform tooling already exists. If you want a realistic assessment for your specific system, talk to CodeNicely for a personalized assessment rather than trust a generic estimate.

When is it clearly time to extract services?

When you have clear ownership boundaries on paper, clean or fixable data boundaries, at least one subsystem with a genuinely different scaling profile or deploy cadence, and enough engineers (usually 30+) that cross-team coordination on a shared deploy is a weekly blocker. If two or more of those aren't true, fix the monolith first.

Found this useful? CodeNicely publishes engineering and product playbooks weekly. Browse the archive or tell us what you're building.