One of the most common questions in software architecture is:
"Would you build this application as a monolith or use microservices?"
At first, the answer might seem obvious. Monoliths are often associated with older applications, while microservices are associated with modern, scalable systems.
But that comparison is misleading. A well-designed monolith can support millions of users, while a poorly designed microservices architecture can become difficult to maintain even with relatively little traffic.
The real difference isn't about which architecture is more modern. It's about how each approach manages complexity, deployment, scaling, data consistency, and team coordination. In this chapter, we'll compare monoliths and microservices, explore their practical trade-offs, and understand how to choose between them.
Let's start with a simple example. Imagine you're building an e-commerce application with product catalog, shopping cart, order management, payments, and inventory functionality.
In a monolithic architecture, these capabilities are implemented as modules within a single deployable application. The application might contain separate components for products, orders, payments, and inventory, but they're packaged and deployed together. For example:
When a customer places an order, the Order Module can directly call the Inventory Module through an in-process function call. The application can also use a single database transaction to update related records when appropriate. This makes communication and transaction management relatively straightforward.
However, because everything belongs to one deployable unit, releasing a change to one module generally requires deploying the entire application.
In a microservices architecture, the application is divided into independently deployable services. For example:
Each service owns its business logic and data, communicating with other services through APIs or asynchronous messages. The Catalog Service can be deployed independently of the Payment Service, and each can scale according to its workload.
But operations that previously happened within one application may now require network calls and coordination across multiple services.
Monoliths simplify interactions within an application, while microservices provide greater independence between parts of the application. This is the central trade-off between the two.
Let's examine what that means in practice.
For a small team building a new product, a monolith is often the simpler choice. Developers can work within one codebase, run the application locally, and debug functionality without coordinating multiple services. Adding a feature that touches orders and payments might involve modifying two modules within the same application. There's no immediate need to design network protocols, configure service discovery, or manage distributed communication.
Microservices introduce these concerns much earlier. Even a simple feature might require changes to API contracts, coordination between services, and additional integration testing.
However, the trade-off can change as the organization grows. Imagine 15 engineering teams working on the same monolithic application. They may regularly modify overlapping code, compete for CI/CD resources, and coordinate releases. If the application has well-defined business boundaries, microservices can reduce some of that coordination by allowing teams to work and deploy independently.
The important distinction is between local development simplicity and organizational scalability. A monolith often provides better simplicity for a small team. Microservices can provide better development autonomy for a larger organization.
Deployment is one of the strongest reasons to consider microservices. Suppose your e-commerce application needs a small change to the product catalog. With a monolith, you generally build and deploy the entire application, even though the change affects only one module. This can increase the scope of testing and the impact of a failed deployment.
With microservices, the Catalog Service can be deployed independently. If a deployment introduces a bug, the team can potentially roll back just that service.
However, independent deployment comes with additional responsibilities. Services must maintain compatible API contracts. Database migrations must be carefully managed. Deployment pipelines need to support multiple applications.
Independent deployment doesn't guarantee that changes are truly independent. If deploying the Catalog Service always requires simultaneous changes to the Cart Service, the architecture still has significant coupling.
The real benefit is independent evolution, not simply having separate deployment pipelines.
A common argument for microservices is that they scale better. That's not always true.
Both monoliths and microservices can scale horizontally by running multiple application instances behind a load balancer. The difference is how precisely resources can be allocated. Consider an e-commerce application receiving:
With a monolith, adding application instances increases capacity for the entire application. Every instance contains the catalog, cart, payment, and other modules, even if catalog traffic is driving most of the resource usage.
With microservices, you can add more Catalog Service instances without increasing Payment Service capacity. This can improve resource efficiency for workloads with significantly different scaling requirements.
But there's a catch. A monolith can also optimize individual workloads through caching, background workers, database tuning, and specialized infrastructure. And microservices can still face shared bottlenecks, such as overloaded databases or message brokers.
Microservices provide more granular scaling, but they don't automatically provide higher throughput or better performance.
Inside a monolith, modules often communicate through direct function calls.
These calls are generally fast and don't require network communication.
In microservices, communication between services often involves serialization, network requests, authentication, and response processing. Consider a checkout request that needs to interact with the Order, Inventory, Payment, and Shipping services. If these interactions happen sequentially, their latencies accumulate. For example:
These are illustrative service-processing times, excluding additional network and orchestration overhead. The overall request might take even longer if services experience network delays or require retries.
Microservices can reduce unnecessary blocking through asynchronous workflows, parallel execution, caching, and better service boundaries. But these optimizations introduce their own complexity. For latency-sensitive applications with highly interconnected operations, a monolith may offer a simpler and faster execution model.
Data consistency is one of the biggest architectural differences. Consider a customer placing an order. The application needs to create an order, reserve inventory, and process payment.
In a monolith using a shared transactional database, related database updates can often be grouped into a single ACID transaction. However, external payment processing still requires handling failures and reconciliation separately.
In microservices, the Order, Inventory, and Payment services typically own different data stores. A single database transaction no longer covers the entire workflow. You may need a Saga pattern to coordinate local transactions and compensating actions. For example:
PENDING status.This workflow must handle retries, duplicate messages, timeouts, and partial failures. Some intermediate states may be temporarily inconsistent across services. Patterns such as transactional outbox, idempotent consumers, and event-driven communication can help manage these challenges.
Here is the same order workflow in both architectures, with one transaction on the left and coordinated local transactions on the right:
The trade-off is clear:
Monoliths often simplify transactional consistency, while microservices require explicit coordination across service boundaries.
This is especially important for financial systems, inventory management, and other workflows where correctness matters more than architectural flexibility.
Microservices are often praised for fault isolation. Suppose the recommendation functionality in an e-commerce application becomes unavailable. In a well-designed microservices architecture, the Recommendation Service can fail while checkout and payment continue operating. Each service can have its own resource limits, scaling policies, and recovery mechanisms.
However, microservices also introduce more failure points. Network connections can fail. Downstream services can become overloaded. Messages can be delayed or duplicated.
A failure in a critical service can spread across the system through synchronous dependencies and aggressive retries. For example, if the Inventory Service becomes unavailable, the Order Service may also stop accepting orders.
A monolith has fewer internal network dependencies, but shared process resources can allow a memory leak or resource exhaustion in one module to affect the entire application instance.
Neither architecture guarantees better reliability. Microservices make certain types of isolation easier, but achieving resilience requires timeouts, circuit breakers, bulkheads, retries, and graceful degradation.
Debugging a monolith is generally straightforward because much of the execution happens within one process. Developers can follow stack traces, set breakpoints, and reproduce many workflows locally.
Microservices complicate this process. A single customer request may travel through five services, producing logs in different applications. Finding the root cause of a failure may require distributed tracing, centralized logging, correlation IDs, and service-level metrics.
Testing also becomes more involved. Instead of relying primarily on unit and integration tests within one application, microservices often require contract tests, service integration tests, and carefully selected end-to-end tests. A reliable CI/CD process must also handle API compatibility between independently deployed services.
These capabilities are manageable, but they require additional investment in engineering infrastructure. For a small organization, that investment may outweigh the benefits of microservices.
Microservices often introduce a larger operational footprint. Each service may require its own deployment configuration, monitoring, alerting, resource limits, and security policies. Organizations may also need API gateways, message brokers, container orchestration, and distributed tracing infrastructure. There are costs associated with inter-service network traffic and maintaining multiple service instances.
A monolith can often be deployed and operated with a simpler infrastructure stack. This doesn't mean monoliths are always cheaper. At a large scale, independent scaling and smaller deployment units may make microservices more resource-efficient and operationally manageable.
But microservices should be adopted because they solve meaningful problems, not because the infrastructure is fashionable.
The choice isn't always between a tightly coupled monolith and dozens of independent services. A modular monolith offers an important middle ground. It's a single deployable application organized into well-defined modules with explicit boundaries.
For example, catalog, orders, payments, and inventory can each have their own internal logic, interfaces, and data ownership rules. Modules communicate through controlled interfaces rather than accessing each other's internal implementation details. This provides many benefits associated with good microservices design without introducing distributed communication.
If the application eventually requires independent deployment or scaling, some modules may become candidates for extraction into microservices. However, that migration isn't automatic. Extracting a module still requires handling data separation, network failures, and distributed workflows.
A modular monolith is often an excellent default when future scale and organizational requirements remain uncertain.
Monoliths and microservices are different ways of organizing software, each with meaningful advantages and limitations. Monoliths generally provide simpler development, deployment, debugging, and transaction management. Microservices provide greater independence in deployment, scaling, and team ownership, but introduce distributed systems and operational complexity.
Neither architecture is universally superior. A well-designed modular monolith can be a better engineering decision than a poorly designed microservices system. The key principle is simple:
Choose the architecture that solves your current problems while keeping future changes manageable.
Understand what you're gaining, what you're giving up, and why those trade-offs make sense for the system you're designing.
10 quizzes