Every second architecture conversation we have starts the same way: "We want microservices." It is rarely a technical requirement. It is usually a reasonable fear — that the current system will not scale, or that one team's change keeps breaking another team's feature.
Those are real problems. Microservices are one solution to them, and frequently the most expensive one available.
What microservices actually buy you
Independent deployability. That is the headline benefit, and everything else follows from it. If four teams can ship on four different days without coordinating a release, you have removed an organisational bottleneck that no amount of clever code structure inside a single deployable will fix.
The second real benefit is independent scaling. If your order-ingestion path handles 400 requests per second while your reporting module handles four, splitting them lets you size infrastructure to each.
What they cost
- Distributed transactions. An operation that was one database transaction becomes a saga with compensating actions. This is not harder to write; it is harder to reason about at 2 a.m.
- Operational surface. Service discovery, distributed tracing, per-service dashboards, certificate rotation, contract versioning. Each is manageable. Together they are a full-time platform function.
- Debugging cost. A single user-visible failure now spans four log streams. Without disciplined tracing from day one, incident resolution time goes up, not down.
- Latency. In-process calls become network calls. Ten of them in a request path is a real performance budget.
If your team cannot yet operate one service well — with alerting, runbooks and a tested rollback — splitting it into eight will not improve matters.
The framework
We score four dimensions with the client. Any two scoring high is usually enough to justify splitting a bounded context out.
1. Team topology
How many engineers work on this codebase, and are they organised into stable teams with distinct ownership? Below roughly fifteen engineers on one product, the coordination cost microservices remove barely exists. Above forty, it dominates everything else.
2. Deployment coupling pain
Count the releases in the last quarter that were delayed because an unrelated module was not ready. If that number is zero, deployment coupling is not your problem. If it is most of them, it is.
3. Divergent scaling profile
Do parts of the system have genuinely different load or resource shapes? A document-generation worker that needs 8 GB of memory for thirty seconds a day has a different profile from an API serving steady traffic. That is a real signal.
4. Divergent change velocity
A pricing engine that changes weekly and a general-ledger posting module that changes twice a year have different risk appetites. Isolating them means the stable component is not dragged through every risky release.
What we usually recommend instead
A modular monolith: one deployable, but with hard internal boundaries. Each module owns its own tables. No module reaches into another module's schema. Communication between modules goes through an explicit in-process interface — the same interface you would expose over HTTP if you later split it.
src/
modules/
sales/ # owns sales_* tables
api/ # public interface — the only entry point
domain/
infrastructure/
inventory/ # owns inventory_* tables
api/
domain/
infrastructure/
shared/
kernel/ # ids, money, dates — no business logic
Enforce it in CI with a dependency-check rule. If inventory imports anything from sales except sales/api, the build fails. That single rule preserves nearly every architectural option you might want later.
The migration path, when you need it
Because each module already owns its data and exposes a narrow interface, extraction is mechanical rather than archaeological:
- Replace the in-process interface call with an HTTP or message-based adapter, keeping the same contract.
- Move the module's tables to their own schema, then their own database instance.
- Deploy the module separately. The calling code never changed.
We have done this for clients three and four years after the original build. It took weeks, not the eighteen-month rewrite that an unbounded monolith would have demanded.
The honest summary
Start with a modular monolith. Enforce the boundaries mechanically. Extract services when a specific, named pain justifies the specific, measurable cost. Architecture is not a status symbol — it is a set of trade-offs you should be able to defend out loud in a room full of people paying for it.