News
Development8 min read

Microservices vs monolith: which architecture to choose for a company application

Microservices get prescribed far more often than they pay off. Here is a sober look at what a monolith and microservices actually mean in practice, what splitting a system really costs to operate, and why a modular monolith is the right answer for most companies.

The question "microservices or a monolith?" comes up at almost every kick-off for a larger project. It is usually asked by someone who read an article last week about how a large technology company split its system into two hundred services and sped up development. The problem is that by the time it split, that company had several hundred developers, a dedicated platform team, and a system that had not been deployable without an overnight outage for years. Your company probably has five to fifteen people and a system that is still being built.

Architecture is not a modernity contest. It is a decision about where you will pay for complexity — inside a single process, or across dozens of processes over a network. Both options have a price, and the second one is far higher than presentations admit. This article is an attempt at a sober comparison, not a defence of one side.

What a monolith and microservices actually mean

A monolith is an application deployed as a single unit. One repository, one build, one database, one process (or several identical instances behind a load balancer). Internally it can be perfectly well divided into modules, layers and domains — but from a deployment perspective it is one unit.

Microservices are the opposite: a system split into independently deployable services, each owning its own data and talking to the others over the network — HTTP, gRPC or messages on a queue. The key phrase is independently deployable. If you have to deploy five services at once because they keep changing each other's interfaces, you do not have microservices. You have a distributed monolith — the worst possible combination: the complexity of a network without the benefit of independence.

This distinction matters more than it seems. Most systems described as microservice-based are in fact distributed monoliths. They came about because someone split the code along technical layers or database tables instead of along business domains.

If two services must be deployed together, they are not two services. They are one service with a slow and unreliable function call in the middle.

Why a monolith is a sensible starting point

For most company systems — an internal system, a portal, a custom e-commerce store, an application for managing an agenda — a monolith is the right choice, and it stays the right choice considerably longer than people usually admit.

The reason is mundane: at the start of a project you do not know where the domain boundaries should run. You find out after a few months in production, once you can see which parts change together and which change on their own. In a monolith, moving a boundary is a matter of refactoring — you move classes, adjust the calls, run the tests. When that same boundary has been cast into a network interface between two deployed services, moving it means a coordinated change, a data migration and API versioning.

A monolith also gives you things that cost a lot of work in a distributed environment: transactions in a single database, debugging with one stack trace, refactoring across the whole domain with one IDE command, running the entire system locally on a laptop. These are not small things — they are most of a development team's daily work. We described how the phases of such a project actually unfold in our piece on how custom software development proceeds step by step.

In short: Treat the monolith as the default and depart from it only when you have a concrete, named reason — not because microservices "are the standard".

The real operational cost of microservices

You do not pay for microservices in code. You pay in operations and in people. Here is what actually changes:

AreaMonolithMicroservices
DeploymentOne pipeline, one artifactA pipeline per service, interface versioning, cross-version compatibility
Debugging a faultOne stack traceDistributed tracing across services, correlation IDs
Data consistencyA database transactionSagas, compensating operations, eventual consistency
Local developmentdocker compose up and doneMock services or a shared development environment
TestingEnd-to-end tests over one systemContract tests between services plus an integration environment
Roles requiredDevelopersDevelopers plus someone who owns the platform and infrastructure
Partial failureThe application runs or it does notPartial failures, timeouts, retries, circuit breakers

Deployment and versioning

Independent deployment sounds like a benefit until you realise it also means independent versioning. If service A calls service B, you have to handle what happens when a new A runs against an old B. That leads to backwards-compatible interface changes, to periods where two API versions exist side by side, and to a discipline nobody in a small team has time to maintain. We covered the principles that help here in our article on API-first architecture for growing companies.

Observability

In a monolith you know why a request failed from the log and the stack trace. In a distributed system a single user action passes through five services, and without distributed tracing and correlation identifiers, finding the cause is guesswork. Observability in microservices is not a nice extra — it is a precondition for being able to operate at all. And it has to be built and maintained before you start needing it.

Data consistency

This is the most underestimated item. Once each service owns its own data, transactions across domains cease to exist. "Create the order, decrement stock, reserve the payment" is no longer one atomic operation but a sequence of steps with compensations in case the third one fails. Code that was ten lines in a monolith turns into a state machine with error handling. We describe similar challenges when connecting systems in our text on system integration of ERP, CRM and an e-shop.

Conway's law: architecture copies the organisation

Melvin Conway formulated a simple observation: the systems an organisation designs mirror its communication structures. In practice this means you cannot choose an architecture independently of how the teams are arranged.

Microservices work when each service is owned by a team that both develops and operates it, and when teams communicate through interfaces rather than through meetings. With five people no such split exists — everyone works on everything. Splitting a system into eight services in a five-person team will not produce independence. It will produce eight pipelines, eight sets of configuration, and one person who checks at every release whether the versions line up.

A practical rule: the number of services should not significantly exceed the number of teams able to own them. If you have one team, have one service — or two, if there is a genuine reason for the second.

The modular monolith as the pragmatic middle ground

The modular monolith is the answer to most situations in which a company considers microservices. It is a single deployment unit divided internally into modules along business domains — orders, stock, invoicing, users — with explicit boundaries.

In practice this means:

  • A module owns its tables. Other modules do not reach into them directly, only through the module's public interface.
  • Dependencies run one way. You forbid cyclic dependencies between modules and enforce it with a tool, not with an agreement made in a meeting.
  • Communication between modules is explicit. An interface call or a domain event — never a direct call into an internal class.
  • Tests can be written at module level. A module has its own test suite that does not need the rest of the system.

The result is a system that has the boundaries of microservices but does not yet have a network between them. When you later find that the "invoicing" module genuinely needs its own release cycle, extracting it is a matter of weeks, not a rewrite. And if you never find that out, you have saved years of operational overhead.

A modular monolith also scales perfectly well — horizontally, with replicas behind a load balancer. Most performance problems in company applications are not caused by architecture but by database queries and missing indexes; we wrote a separate piece on scaling a web application as the company grows.

Concrete signals that justify splitting out a service

Split services out one at a time, and for a reason you can name. The real signals:

  • A different load profile. Generating PDFs or processing images demands different resources and different scaling from the rest of the application.
  • A different release cadence. A component that changes daily holds back a system released once a month — and the other way round.
  • A different owner. A part of the system is taken over by a separate team with its own operational responsibility.
  • Regulatory or security isolation. Processing of sensitive data that should be separated at the infrastructure level too.
  • A different technology. Part of the problem genuinely is better solved in another language or runtime.

What is not a signal: "we want to be scalable", "microservices are modern", "we want to use a different technology because we are curious about it".

Migration risks and how to reduce them

The most expensive mistake is not choosing a monolith. It is deciding to rewrite a working monolith into microservices in one go — stopping feature development for a year, building a new system and then switching over. Such projects typically overrun, the original system keeps changing in the meantime, and the new one never catches up with reality.

The safer approach is incremental: first clean up the module boundaries inside the monolith, then pick the one module with the strongest reason and move it out, run both systems in parallel, and migrate the data only once it is proven. We describe the same logic of gradual steps in our article on migrating a legacy system to a modern platform.

Caution: If you do not have automated tests, monitoring and a reliable CI/CD pipeline, microservices will not improve the situation — they will simply multiply the number of places where those things are missing.

Summary

Microservices solve an organisational problem that large companies have: too many people per deployment unit. If you do not have that problem, you are solving something that does not trouble you, and paying for it with operational complexity you will feel every single day.

For a mid-sized company system and a team of up to roughly ten people, the right answer is almost always a modular monolith with honestly maintained domain boundaries. From there you can move towards services when a concrete reason appears — and it will then be a controlled extraction, not a leap into the unknown. When designing the architecture of company systems in custom development, we therefore start with the question of where the domain boundaries run, not with the question of how many services there will be.

If you are weighing up whether your system is ready to be split, or you are planning a new one and want to avoid premature fragmentation, get in touch and we will go through your situation in concrete terms.

INTERFASE