Technology Advisory
·7 min read

Modular monolith vs microservices: how to choose for a team under 20 engineers

The most expensive architecture decision a small team makes is usually the one made to avoid a problem it does not yet have. Microservices are a real solution to a real problem: dozens of engineers stepping on each other in one codebase. If you have six engineers, that is not your problem, and adopting the solution anyway will give you a different problem that is much harder to see coming.

Modular monolith vs microservices: how to choose for a team under 20 engineers
In brief
  • Microservices solve a coordination problem: many teams working on one system. Under about twenty engineers, that problem mostly does not exist.
  • A modular monolith is one deployable unit with strict internal boundaries. Done well, it gives you most of the organizational benefit of services with almost none of the operational cost.
  • The real cost of microservices is not the code. It is the network, the data consistency, the observability, and the deployment machinery that a small team then has to run.
  • Choose based on team size, deployment needs, data coupling, and how many people you have to run infrastructure. Not on what large companies do.
  • Keep the door open: a modular monolith with clean boundaries can be split later. A tangled monolith cannot, and neither can a tangled set of services.

The question behind the question

When a small team asks "should we use microservices," they are rarely asking about architecture. They are asking one of three other things.

"Will this scale?" Almost always yes, either way. A well-built monolith on a single database handles far more traffic than most startups will ever see. Scaling problems at small companies are nearly always a slow query or a missing cache, not an architectural ceiling.

"Will this look credible?" To investors, to senior hires, to customers doing technical due diligence. This one is real but backwards. Experienced reviewers are far more worried by a six-person team running thirty services than by one running a clean monolith. The former signals a team spending its time on plumbing.

"Will we regret it later?" This is the good question, and the rest of this article is the answer to it.

What microservices actually cost

Microservices are usually described by their benefits: independent deployment, independent scaling, technology freedom, fault isolation. Those benefits are real. They are also mostly benefits for organizations with a specific shape: many teams, each owning a service, each needing to ship without waiting for the others.

The costs are less often described, and for a small team they dominate.

Every function call becomes a network call. It can fail, time out, or return stale data. Code that was three lines in a monolith becomes retries, circuit breakers, timeouts, and a conversation about what to do when the downstream service is unavailable.

Every transaction becomes a distributed transaction. Or, more honestly, stops being a transaction. Updating an order and decrementing inventory used to be one atomic operation. Across two services it is a saga, an outbox, an eventual-consistency story, and a set of edge cases where the two disagree.

Debugging becomes archaeology. A request touches six services. Reproducing a bug means reproducing the state of six things. Without distributed tracing, structured logging with correlation IDs, and someone who knows how to read them, a small team loses days to problems that took minutes in a monolith.

Deployment becomes infrastructure. Thirty services need thirty pipelines, thirty sets of configuration, service discovery, a way to run the whole thing locally, and someone whose job is keeping that machinery working. At a large company that person exists. At a six-person startup, that person is also the person who was supposed to be building the product.

Every boundary you draw wrong is expensive to move. And you will draw some wrong, because the correct boundaries only become clear once the product exists. In a monolith, moving a boundary is a refactor. Across services, it is a migration with an API change and a data move.

None of this is an argument that microservices are bad. It is an argument that they are a trade: you pay operational complexity to buy organizational independence. If you do not have the organizational problem, you are paying for nothing.

What a modular monolith actually is

A modular monolith is one deployable unit, one process, usually one database, with strict internal boundaries between modules. The word doing the work is strict.

Each module owns a slice of the domain: billing, accounts, catalog, notifications. Each module has a small public interface that the others call. Each module owns its own tables, and no other module touches them directly. Cross-module communication goes through that interface or through in-process events, never through a shared table or a reach into someone else's internals.

A single deployable unit divided into four modules with narrow interfaces and per-module data ownership.

This is not a "monolith with folders." Most monoliths have folders. The difference is enforcement. The build fails if the billing module imports from the accounts module's internals. The database schema makes it awkward to write a query that joins across module boundaries. The team treats the boundary as a contract, not a suggestion.

Done this way, you get most of what microservices promise at the organizational level: clear ownership, changes that stay contained, the ability to reason about one area without holding the whole system in your head. You get none of the network, none of the distributed data problem, none of the deployment machinery. You deploy one thing. You debug one process. Transactions are transactions.

And, critically, when a module genuinely needs to become its own service later, because it has different scaling needs or a separate team now owns it, the boundary is already there. The extraction is a bounded piece of work instead of a rewrite.

The decision table

There is no single threshold. There is a set of signals, and the honest answer for most teams under twenty is that few of them point toward services yet.

SignalPoints toward modular monolithPoints toward microservices
Team sizeUnder ~20 engineers, one or two teamsMultiple teams that need to ship independently
DeploymentOne release train is fineTeams are blocked waiting on each other's releases
ScalingWhole system scales together well enoughOne component needs 50x the resources of the rest
DataMost operations touch several domains togetherDomains are naturally separate with little shared data
TechnologyOne stack serves every partA component genuinely needs a different runtime
Operations capacityNobody's full-time job is infrastructureA platform team exists to run the machinery
Failure isolationA bug taking the whole app down is survivableOne component must keep running when others fail
Decision table comparing modular monolith and microservices across team size, deployment, data, and operations.

The pattern is that microservices become the right answer when the organization has separated, not when the code has grown. Code size is handled by modules. Organizational size is what services are for.

Note

"One component needs 50x the resources" is the one technical signal that legitimately justifies an early split. A video transcoder, an ML inference endpoint, or a report generator that runs for minutes can be worth extracting even in a small team, precisely because it is one boundary, not thirty.

The middle path: extract, don't decompose

The framing "monolith versus microservices" hides the option most successful small companies actually take: a modular monolith plus a small number of deliberately extracted services.

Start with the monolith. Enforce the boundaries. When a specific module earns extraction, because it has a different scaling profile, a different team, or a different failure requirement, extract that one. Leave the rest.

Two years in, this looks like one core application and two or three satellite services, each of which exists for a reason someone can articulate. That is a healthy architecture. It is not a "transition to microservices"; it is the end state, and it is fine.

The alternative, decomposing everything up front because that is what the architecture diagram is supposed to look like, produces the distributed monolith: thirty services that all have to deploy together because the boundaries were drawn before anyone knew where they belonged.

How to keep the door open

If you build the monolith, build it so that a future split is possible. Four disciplines do most of the work.

Enforce module boundaries mechanically. Use whatever your language offers: package visibility, architecture tests, a linter rule, a build step that fails on forbidden imports. If the boundary lives only in a document, it will not survive the first deadline.

Give each module its own tables. Foreign keys across module boundaries are the thing that makes extraction painful later. Prefer references by ID with the lookup going through the owning module's interface.

Communicate through interfaces and events. If billing needs to know an account was created, it should subscribe to an event, not query the accounts table. In-process events cost nothing now and become a message queue later with minimal change.

Write down what each module owns. One page per module: what it is responsible for, what it exposes, what it depends on. This is the document that makes the eventual extraction a plan instead of a discovery.

When it is time

You will know a module is ready to become a service when you can answer yes to all of these: it has a clear owner; it has a scaling or reliability need the rest of the system does not share; its interface has been stable for a while; and someone has the capacity to run it as a separate deployable. If any of those is a no, the extraction will cost more than it returns. Wait.

Getting the boundaries right

The hard part of a modular monolith is not the tooling. It is deciding where the lines go. Draw them along the wrong seams and you get a monolith that is modular on paper and tangled in practice, which is the worst of both.

This is the part that benefits most from an outside view, because the people building the system are usually too close to its current shape to see where it wants to be cut. Our modular monolith, microservices and scalability design work is exactly this: finding the seams, defining the contracts, and setting up the enforcement so the boundaries hold. If you are about to make this call for a new build or a rewrite, book a working session and bring the current diagram. We will tell you honestly whether it needs services, and if so, which one.

ArchitectureMicroservicesModular MonolithScalability
Share
Working on something similar?

Tell us the constraint, and we will sketch the approach.

A short technical conversation, no pitch deck. Bring the workflow you want to shorten and we will outline the approach, the likely effort, and how we would measure it.