Start by counting your teams, not your features. Microservices exist to let many teams deploy independently without coordinating a single release. That is an organisational benefit, not a technical one. If you have one or two teams, a well structured modular monolith will almost always serve you better, and you can split it later when the org actually forces the question.
The problem microservices are for
The honest reason large companies moved to microservices is that a hundred engineers cannot share one deploy pipeline without stepping on each other constantly. When ten teams all merge into one artefact, every release becomes a negotiation. Someone's half finished feature blocks everyone's fix. The build takes forty minutes and fails for reasons unrelated to your change. Microservices break that gridlock by giving each team its own deployable unit, its own release cadence, and its own blast radius.
Notice what that reasoning has nothing to do with: your feature list, your traffic, or how modern your stack looks. Splitting a service because it has "a lot of features" is a category error. Features do not deploy themselves. Teams do. The right question is never "is this domain complex enough to deserve its own service" but "does a separate team need to ship this on its own schedule".
The costs nobody quotes you
Every network call you introduce between two pieces of your own code is a cost you pay forever. Inside a monolith, a function call either succeeds or throws, and it does so in microseconds. Across a service boundary, that same call can time out, arrive twice, arrive out of order, or half complete. You now own retries, idempotency, circuit breakers, and a tracing system to work out which of nine hops was slow.
- Operational surface. Ten services means ten deploy pipelines, ten sets of dashboards, ten on call rotations, ten dependency upgrade backlogs. This work scales with service count, and it does not disappear on a quiet week.
- Distributed debugging. A bug that would be one stack trace in a monolith becomes an archaeology exercise across logs from four systems with mismatched clocks. You will spend real engineering weeks building the observability you used to get for free.
- Data gravity. The moment a transaction spans two services, you have lost your database's atomic guarantees. Sagas and eventual consistency are not free architecture patterns, they are a tax on every feature that touches more than one service.
None of this is an argument that microservices are wrong. It is an argument that they are expensive, and that you should only buy them when the thing they fix is a problem you actually have.
The line sits near three teams
In my experience the crossover point is somewhere around three teams working on the same codebase. Below that, coordination is cheap. Three teams can agree on a release window, review each other's work, and unpick a bad merge in an afternoon. The overhead of splitting into services would cost you more than the coordination you would save.
Above that line, coordination cost grows faster than most people expect, and independent deployability starts to pay for its overhead. But reaching for microservices at two teams because you might one day have twenty is premature. You will carry the operational tax for years before the org grows into it, and you will make the wrong cuts because you do not yet understand your domain.
Splitting a system you do not understand along the wrong lines gives you distributed spaghetti: all the operational cost of microservices with all the coupling of a monolith, and no way back.
Build the modular monolith first
The strongest default for a small team is a monolith with genuine internal boundaries. One deployable artefact, one database, one place to look when something breaks, but organised into modules that talk to each other through explicit interfaces rather than reaching into each other's tables and internals.
Done properly this gives you most of what people think they need microservices for. You get clear ownership, testable seams, and a codebase where a change in billing cannot silently corrupt search. What you avoid is the network, the operational multiplication, and the irreversible data split. And when a boundary genuinely needs to become a service later, a clean module is a clean thing to lift out. A tangled one was never going to split well no matter what you did.
How to draw the boundaries
The unit that matters is the business capability, not the technical layer. Do not carve out a "user service" and an "email service" because those are nouns you can name. Carve along the lines where the business changes independently: ordering, fulfilment, billing, catalogue. These map to how teams form and how requirements arrive.
- Follow the language. Where the words change meaning, a boundary lives. If "account" means something different to the finance team than to the support team, that is two contexts, not one shared model to be jammed together.
- Follow the data ownership. A good boundary owns its data outright. If two candidate modules both need to write the same table constantly, they are one module wearing two hats.
- Follow the rate of change. Parts of the system that always change together belong together. Parts that change on different clocks, driven by different people, are your natural seams.
- Prefer coarse. When unsure, make the boundary larger. Merging two modules later is a refactor. Splitting a wrongly merged one across a network is a migration project.
A practical decision
So before the next architecture debate, answer three questions honestly. How many teams will own this in eighteen months? Is anything actually forcing independent deployment, such as different release cadences or compliance isolation? And do you understand the domain well enough to know where the real seams are? If the answer is one or two teams, nothing forcing a split, and a domain you are still learning, build the modular monolith and get on with shipping. You can always extract a service the day the org genuinely needs one, and you will make a far better cut once the boundaries have revealed themselves.
If you want an outside read on where your real seams sit before you commit, that is exactly the kind of thing our custom software development team looks at, and you are welcome to book a free consultation to talk it through.
