$ emrebener
home topics architectural patterns why comparing monoliths and microservices makes no sense

Why Comparing Monoliths and Microservices Makes No Sense

author: emre bener read time: 9 min about: monolithic architecture, microservices, software architecture
published: updated: mentions: modular programming, distributed computing, non-functional requirement, martin fowler

Treating monolithic architecture and microservices as alternatives to each other is nonsensical, because architecture is about fit-for-context, not ideological preference.

We can still get some valuable insight by evaluating their ups and downs. I’ll start from basics, make some clear definitions, and build up to my point.

What is Software Architecture?

I want to start with a simple question. Truly, what is software architecture? I define it as:

💡 The high level design of a system that decides its structure, behaviour, and constraints.

By “high level”, what I mean is that we are not concerned with details within the project such as use of various design patterns on a low level. For example, it doesn’t change the overall architecture of the system whether or not you choose to use mediator pattern with CQRS.

The software architecture is concerned with the big picture: how components of the system are structured (service boundaries), how they communicate with each other (synchronous vs. asynchronous, event-based..), and so on.

The software architecture you choose typically has a significant impact on the quality attributes of your system (more on these later). It is also expensive to make architectural changes down the road, unless you specifically designed the system in anticipation of an eventual change. Get it right from the start.

Before getting into “comparison”, I will clearly define monolithic and microservices architectures.

Monoliths

A monolithic software system has a single deployment unit. For example, if you only build a single project and deploy it as a single app, that’s a monolith.

A monolith

Of course, that doesn’t mean you can’t horizontally scale in production by adding more instances. In a HA (high-availability) setting, each instance would still be the same deployment unit.

Load balancerMonolithic instance 1Cache storeMonolithic instance 3Monolithic instance 2Stateless app instancesLoad balancerMonolithic instance 1Cache storeMonolithic instance 3Monolithic instance 2Stateless app instances

Horizontally scaling a monolith

Another attribute of monolithic projects is that communication between components is done via direct method calls, which is the simplest approach there is. It’s simple and effective.

In a monolith system, there is typically only a single database, which ensures immediate consistency.

Upsides of monolithic architecture includes:

  • Easy deployments
  • Simple transaction management (immediate consistency, no need for distributed transactions etc.)
  • Reduced overall complexity

💡 Note: While monoliths often rely on immediate consistency, they can still use eventual consistency internally through async workflows or background processing. I’m assuming the default and typical scenarios.

Microservices

On the other hand, microservices architecture produces many deployable units (one for each service). These deployment units are logically and physically isolated services that come together to form the microservices system.

Amazons microservices architecture 2008 > Amazon’s microservices architecture (2008)

Because it is a distributed system, the communication between the services has to use network calls. This brings challenges, such as serializing data and sending it over the network, integrating with various communication protocols, authenticating between the services, and so on. This is why communication inside of a monolith system is much simpler.

When it comes to scaling, microservices architecture allows to scale each service individually, which gives granular control over how the system evolves and overcomes bottlenecks over time in a resource-efficient way.

The upsides of microservices architecture include:

  • Better scalability
  • Independent deployability
  • Zero-downtime deployments
  • Isolation of data
  • Organizational autonomy (easier work distribution in a team)

The downsides are heavier.

Eventual consistency is a significant problem to solve in microservices, since each microservice has its own database. Synchronizing data between services is typically eventually consistent unless you use distributed transactions or two-phase commits, which introduce a whole new set of problems. If data is temporarily wrong across services, that leads to stale reads, race conditions, user-visible glitches, and tricky edge cases that make reasoning, testing and debugging harder. operational overhead (observability, service discovery, CI/CD complexity, etc.) is another real burden. Microservices environments are complicated for a reason.

Microservices ArchitectureOrders ServiceUsers ServicePayments ServiceOrders DBUsers DBPayments DBnetwork callnetwork callMicroservices ArchitectureOrders ServiceUsers ServicePayments ServiceOrders DBUsers DBPayments DBnetwork callnetwork call

Monoliths vs. Microservices

Two of the most popular architectural styles, then. Which one is better? Which one should you use? It’s been a hot topic for years.

Monolith vs microservices

It doesn’t make sense to compare them head-on. They’re different approaches that suit different contexts.

Netflix couldn’t operate on a monolith. For the scale and traffic of their operation, they need microservices (hundreds to over a thousand of them). Does this mean microservices architecture is better than monolith architecture? Of course not! That is not a logical inference.

Another example. Let’s say Joe (a fictional character) implemented a project in monolithic architecture and it worked well for his use case. Does this mean microservices architecture is unnecessary or inferior? We can’t draw that conclusion.

Microservices are better suited to high traffic and complex systems, but this does not make monolithic architecture inferior. Monoliths are often the better choice for simpler, small-to-medium projects. Different tools, different jobs.

💡 The right choice depends on your situation and needs.

What really drives the decision is the “architectural drives”.

Architectural Drives

These are the factors that drive our architectural decisions:

  • Technical Constraints: The design choices that have been already done by the development team that are now hard to reverse or refactor.
  • Business Constraints: Non-negotiable boundaries affecting the business side of the project (e.g., deadlines and budget).
  • Functional Requirements: These describe what problems the system should solve (e.g., new user accounts should be required email confirmation).
  • Quality Attributes (Non-Functional Requirements): AKA the “ilities”, those are decisive attributes in modern systems. Here is an arbitrary list: scalabilityavailabilitytestabilitymodularitymaintainabilitysecurityobservabilitycost efficiency, and so on.

Fallacies of Distributed Computing

Many teams underestimate the true cost of microservices, and most of the complexity people attribute to “microservices” is actually the inherent complexity of distributed systems themselves. When you design a distributed system, there are conditions you have to anticipate. The fallacies of distributed computing:

  1. The network is reliable (network calls can and will fail due to timeouts, dropped connections, partial outages, or transient errors.)
  2. Latency is zero (every network call adds non-trivial latency compared to in-process method calls, and this latency compounds across service boundaries.)
  3. Bandwidth is infinite (large payloads, chatty communication patterns, and high request volumes quickly become bottlenecks.)
  4. The network is secure (every service-to-service call is a potential attack surface that requires authentication, authorization, and encryption.)
  5. Topology doesn’t change (services are constantly being deployed, scaled, restarted, or relocated, which affects routing and availability.)
  6. There is one administrator (in practice, there will be multiple teams own different services, each with their own priorities, release schedules, and operational practices.)
  7. Transport cost is zero (serialization, deserialization, retries, timeouts, and network hops all introduce real performance and operational costs.)
  8. The network is homogeneous (services may run on different runtimes, languages, platforms, or infrastructure with varying failure modes.)

If you choose to implement microservices architecture, you should be aware of how different the constraints are compared to a monolith system. These fallacies highlight why distributed systems demand fundamentally different design, testing, and operational practices than monolithic systems.

The Middle Ground: Modular Monolith

💡 Wouldn’t it be great if we could combine the best of two worlds?

You can. Take the physical architecture of a monolith (single deployment unit, immediate consistency..) and combine that with the logical architecture of microservices (clearly defined, isolated components that together form the system), and you have yourself a modular monolith.

In a modular monolith, the system is deployed as a single application, but internally, it is composed of well-defined modules with explicit boundaries. Each module owns its own domain logic and data, and interactions between modules are carefully controlled. The goal is to prevent accidental coupling while keeping the operational simplicity of a monolith.

Modular monolithSingle deployment unitOrders moduleUsers modulePayments modulemethod callmethod callShared databaseModular monolithSingle deployment unitOrders moduleUsers modulePayments modulemethod callmethod callShared database

These boundaries aren’t just conceptual; they’re enforced. This can be achieved through techniques such as:

  • Strict dependency rules between modules
  • Clear public interfaces and contracts
  • Preventing direct access to another module’s internal data or implementation details

Without enforcement, a modular monolith quickly degenerates into a “big ball of mud”, where boundaries exist only in documentation and are routinely violated in code.

A well-designed modular monolith provides many of the benefits people often seek in microservices: high cohesion, low coupling, independent development of features, and a system that is easier to understand and evolve. At the same time, it avoids the inherent complexity of distributed systems: no network calls, no service discovery, no eventual consistency across services.

A modular monolith also creates natural service boundaries. When the time comes to extract microservices, these modules often map directly to future services, making the transition incremental and far less risky than a big-bang rewrite.

Monolith-First Approach

You shouldn’t start a new project with microservices, even if you are sure that your application will be large enough to make it worthwhile. — Martin Fowler

Most successful microservices projects started as monoliths, and evolved over time into microservices. When the monolith grows too large to maintain, that’s your signal to break it down into microservices.

Monolithic to microservices transition

The reason it’s better to break up a monolith system into microservices instead of starting with microservices from the start is that it’s easier to determine the service boundaries when working in a monolith (or rather, modular monolith) system. These boundaries will then directly translate to microservice boundaries. It is significantly difficult to get the boundaries right when you start with microservices from the scratch, and refactoring those service boundaries in a microservices environment is difficult.

Depending on how you architected your monolith, you may have a smooth transition into microservices, or you might have a terrible experience. If the tight-coupling and the lack of boundaries is too bad, it might not even be feasible to break it down into microservices. Modular monolith architecture makes this transition as smooth as possible.

My recommendation: start your projects as modular monoliths even when you’re certain you need microservices, and evolve them into microservices over time. Even if you never split it apart, a modular monolith still pays off in the clean, maintainable architecture it gives you.

Bonus: Addressing Some Misconceptions

Microservices is too complicated, therefore monolith is better.

Complexity should not be the decision maker. You should ask whether or not you need microservices. If you don’t need microservices to operate your project, that doesn’t invalidate microservices for everyone else.

Monolithic architecture is outdated/old

Software architecture patterns are not a fashion trends. You shouldn’t choose your architecture based on its popularity or trendyness. You should decide based on your needs.

Monoliths are not scalable

While I understand the point of this argument, it’s not entirely accurate. Monoliths are scalable (both horizontally and vertically), they just don’t let you individually scale different parts of the system (as opposed to microservices). Therefore, they are less resource-efficient when it comes to scaling. For example, when one part of a monolith becomes a bottleneck, you can’t just scale that part; you have to scale the entire application.