In the world of software development, it’s easy to get the impression that a modern application should consist of microservices. One service for handling users, another for orders, a third for payments, and another for notifications. Each component can be developed and deployed independently, and scaled whenever it actually needs to be.
On a diagram, it looks perfect. Each part of the system has its own responsibilities, teams can work in parallel, and a failure in one service shouldn’t bring down the entire application.
The problem starts when the diagram meets reality. Instead of one application, the team maintains a dozen services, several communication mechanisms, numerous deployment processes, and additional logs, monitoring, permissions, and configurations. A simple business operation requires communication between several components. Testing becomes more difficult, and finding the cause of an error means tracing the entire flow through the system.
Does this mean microservices are a bad solution? No. In the right conditions, they can significantly improve how large products are developed. But they are neither an automatic stage in an application’s maturity nor a guarantee of better scalability.
Good architecture isn’t the one with the greatest number of services. It’s the one that lets you develop a product without unnecessarily increasing costs, risk, and complexity.
That’s why, before making a decision, it’s worth understanding how monoliths, modular monoliths, and microservices differ, what problems they solve, and what new responsibilities they introduce.
What exactly is a monolith?
A monolith is an application whose main components are developed and deployed as a single unit. It can contain many modules, support different business processes, and use an extensive internal architecture. So the fact that a system is a monolith doesn’t mean that all the code is in one file or that the application is chaotic.
An example might be a system that handles sales, customers, products, orders, payments, and reporting. All these functions can be part of a single application that uses a shared database and is deployed as one product.
This structure has some clear advantages. In many cases, it’s easier to run, test, and deploy. Calls between modules can happen directly within the application process, without sending requests over a network. Transactions involving several areas of the system can be simpler to implement when they use a shared database.
A monolith can also reduce the number of infrastructure components that need to be managed. There’s no need to configure a separate deployment process, monitoring, and communication for every small function.
Of course, monoliths have limitations too. If new features are added over the years without controlling dependencies, the code becomes increasingly difficult to understand. A change in one place can cause problems elsewhere, and deploying a small feature requires testing and releasing the entire application.
But this isn’t an unavoidable characteristic of a monolith. Often, the problem isn’t the way the application is deployed, but its internal structure.
Not all monoliths are created equal
Architecture discussions often lump all monolithic applications together. Yet there’s an important difference between a system whose modules have clearly defined boundaries and an application where every component can freely depend on the others.
The first is a well-organized structure. The second can turn into what’s known as a big ball of mud—a system with unclear boundaries, numerous dependencies, and logic that’s difficult to trace.
Imagine a sales application where the customer, product, order, and payment modules each have their own responsibilities. The orders module uses a defined interface to the customers module, rather than relying on random parts of its internal implementation. Changes in one area shouldn’t require uncontrolled modifications throughout the system.
If, on the other hand, every module directly modifies other modules’ data, uses their internal classes, and assumes details about how they work, the application gradually loses its structure.
This is where the modular monolith comes in.
Modular monolith: a compromise worth considering
A modular monolith remains a single deployable application, but its internals are divided into clearly defined modules. Each one is responsible for a specific business domain, has clearly defined boundaries, and provides other parts of the system with a controlled way to interact with it.
In practice, this could mean modules responsible for customers, a product catalog, orders, billing, or notifications. Each has its own logic, and access to its internal mechanisms is restricted.
The key difference is that logical boundaries don’t have to mean infrastructure boundaries. Modules can be well separated in the code even though the application is still run and deployed as a whole.
This model helps organize responsibilities, limit dependencies, and prepare the system for further development without incurring all the costs of a distributed architecture.
A modular monolith can be a particularly good solution when:
-
the product is developed by one team or several teams that work closely together;
-
the system’s business boundaries are still taking shape;
-
most features use the same data and need to preserve its consistency;
-
there’s no need to scale individual areas independently;
-
the current deployment process works well enough;
-
the company wants to retain the option of extracting selected modules later.
That last point is especially important. Starting with a modular monolith doesn’t have to close the door to microservices. If module boundaries are well designed and business needs change, a specific domain can be extracted into a separate service in the future.
However, this isn’t an automatic, one-click migration. You need to consider data storage, dependencies, transactions, communication, and responsibility for maintaining the new component. Good modularity can make this process significantly easier.
Microservices: what do they actually offer?
A microservices architecture divides an application into smaller services that perform specific business functions and can be developed and deployed independently. Services communicate with one another through defined interfaces, often via APIs, messages, or events.
This allows the team to change one service without having to deploy the entire application. Individual components can also use different technologies or be scaled independently if their workloads and requirements genuinely differ.
For example, an e-commerce platform may have a separate product search service whose workload grows much faster than that of the user account management module. In a microservices architecture, you can increase the resources allocated to the search service without having to scale every other part of the system.
A similar situation may arise when one area of a product requires particularly frequent deployments, another has distinct availability requirements, and yet another is developed by an independent team.
Microservices can therefore help in several specific areas:
Independent deployments. A change to one service does not have to require releasing all the other components, provided their contracts remain compatible.
Independent scaling. You can increase the resources of only the services that need them, instead of scaling the entire system.
Distinct team responsibilities. Teams can independently develop specific areas of a product if the boundaries of responsibility are genuinely well designed.
Isolation of some problems. A properly designed service can limit the impact of certain failures on the rest of the system. However, this does not happen automatically. If the other components cannot operate without an unavailable service, the problem can still affect a significant part of the application.
Technological flexibility. Individual services can be tailored to different technical needs, although maintaining multiple technologies also increases costs.
It is worth noting that none of these benefits comes solely from the number of services. They require appropriate architectural boundaries, automation, monitoring, a testing process, and team expertise.
The cost of splitting up a system: what lies beyond the diagram?
The biggest mistake when planning microservices is comparing them solely on the basis of code structure. Splitting up an application changes not only how it is implemented, but also how it is run, tested, deployed, and troubleshot.
In a monolith, one function can call another within the same process. In a distributed system, communication between services often means network communication, which can be delayed, fail, or be interrupted at an unexpected moment.
New questions arise. What should you do if service A performed an operation, but service B did not receive a response? Will retrying the request cause the same operation to be performed twice? What happens if part of a process succeeds, but the remaining components do not register the change?
These are not rare, theoretical problems. In systems that handle payments, orders, bookings, or inventory, they can lead to real business discrepancies.
There are also maintenance costs:
-
more components to monitor;
-
separate configurations and deployment processes;
-
the need to trace requests as they pass through multiple services;
-
additional communication and error-handling mechanisms;
-
integration and contract tests;
-
API version management;
-
access control between components;
-
a greater number of potential points of failure;
-
the need for expertise in distributed systems.
This does not mean that microservices are always more expensive over their entire lifecycle. In a sufficiently large system, they can reduce coordination costs and speed up development. However, the initial operational cost is real and should be taken into account when making a decision.
If a company lacks deployment automation, good monitoring, and incident response procedures, splitting up an application can increase complexity faster than it delivers benefits.
When do microservices start to make sense?
There is no single universal number of users, requests, or developers beyond which you should switch to microservices. The decision depends on the type of product, its workload, the organizational structure, and the actual limitations of the current architecture.
However, there are situations in which it is worth seriously considering splitting off part of the system.
1. Different areas have significantly different workload profiles.
If one function requires significantly more computing power than the others, independent scaling can deliver measurable benefits. However, you should first confirm that the problem cannot be solved with simpler methods, such as query optimization, caching, a task queue, or increasing the resources allocated to the entire application.
2. Teams need to develop different areas independently.
In a large organization, a shared codebase and a single release process can make it harder for multiple teams to work effectively. Separating responsibilities can improve autonomy, provided the services have stable boundaries and clearly defined contracts.
3. Different functions have different availability requirements.
Some areas of a product may require exceptional resilience to failures, while others can be temporarily unavailable without halting the most important processes. Independent services make it possible to tailor scaling, deployment, and recovery strategies to a specific area.
4. One module requires a different technology or processing approach.
Sometimes a particular task has justified requirements that would be inefficient to meet in the main application. Splitting it off can then limit the impact of those requirements on the rest of the product. However, it is worth remembering that an additional technology stack also means additional maintenance responsibilities.
5. Business boundaries are already well understood.
When it is clear where one area's responsibility ends and another's begins, it is easier to design independent services. When boundaries are unclear and frequently change, defining them prematurely can lead to costly dependencies and constant interface changes.
The most important principle is: split off a service because it solves a specific problem, not because another box on the diagram looks modern.
When is a modular monolith the better choice?
A modular monolith is often the better option when a company needs to organize its application but has no compelling reason yet to distribute it across many independent services.
This may apply to a new product whose business model is still evolving. At the beginning, it is difficult to predict which features will become the most heavily loaded, which will require independent deployments, and where the boundaries of responsibility will ultimately lie.
The same applies to a medium-sized business system in which most operations involve shared data. Splitting the logic into several services can introduce additional data consistency and transaction problems without providing enough independence in return.
A modular monolith is also worth considering when a company has limited operational resources. A single deployment process and a simpler monitoring model may be a more sensible solution than maintaining a dozen services, each of which requires its own configuration and procedures.
This does not mean leaving everything in one application without a plan for the future. A well-designed modular monolith requires discipline: clear module boundaries, controlled access to data, and limits on unnecessary dependencies.
If these principles are followed, a company can benefit from a simpler deployment model while preparing the system for the possible extraction of selected features in the future.
Scalability Is About More Than the Number of Servers
One of the most common arguments in favor of microservices is scalability. However, it is worth distinguishing between two different concerns: technical scalability and an organization's ability to develop its product.
Technical scalability means the ability to handle a higher workload. In many cases, a monolithic application can be run on multiple instances, using load balancing, caching, job queues, and a well-designed database. Such a system can handle significant traffic without being split into dozens of services.
Of course, not every monolith can be scaled in any way. Problems may result from storing state in process memory, a shared database, costly synchronous operations, or limitations of a particular component. First, however, it is worth determining what is actually creating the bottleneck.
Organizational scalability is a different matter. If many teams work on a product and regularly block one another during changes and deployments, dividing responsibilities can bring benefits even when system performance is sufficient.
In practice, it is therefore worth basing the decision on measurements. Is the application experiencing performance problems? Are deployments taking too long? Do teams have to coordinate every change? Does a particular feature need different resources? Do failures in one module affect the others?
The answers help distinguish an architectural problem from an infrastructure, code quality, or work organization problem.
Migrating to Microservices: Why You Shouldn't Split Everything at Once
If the current application is indeed beginning to limit product development, migrating to microservices does not have to mean rewriting the entire system in one go.
A safer approach is often to extract specific areas gradually. First, determine which module has a sufficiently clear responsibility, what data it handles, and how it works with the other parts of the application.
Next, you can design the communication boundary, prepare tests, and gradually transfer responsibility to the new service. This makes it possible to validate assumptions within a limited area before the company commits to further changes.
For example, a sales system may initially include a reporting module that puts a heavy load on the database and slows down user operations. Instead of splitting the entire system, you can first assess whether reporting can be moved to a separate process or service, possibly using data prepared asynchronously.
Such an extraction makes sense if it actually reduces the load on the main application or enables reporting to be developed and scaled independently. However, if the reports are simple and their workload is light, an additional service may be unnecessary.
That is precisely why a migration should start with the problem and the desired outcome, not with the assumption that all modules will eventually have to become microservices.
It is also worth remembering that extracting a service does not end with moving the code. Data, communication, authorization, monitoring, deployments, testing, and responsibility for failures all need to be addressed. Only then does the new component become a truly independent part of the architecture.
How to Make a Decision: A Practical List of Questions
Before choosing an architecture, it is worth answering a few questions. They will not replace technical analysis, but they can help structure the decision.
1. What specific problem are we trying to solve? Is it performance, deployment time, dependencies between teams, availability, or difficulty maintaining the code? If we cannot identify the problem, it is difficult to assess whether microservices are the right solution.
2. Is the problem actually caused by the architecture? A slow application may need query optimization, while difficult deployments may be due to a lack of testing and automation. An architectural change will not replace these efforts.
3. Are the business boundaries clear enough? If modules constantly rely on one another's internal data and rules, extracting them into independent services may cement an incorrect division.
4. Do we need independent scaling or deployment? If all parts of the system are changed and loaded in similar ways, the benefit of independence may be small.
5. Do we have the tools and skills to maintain a distributed system? Monitoring, logs, request tracing, automated deployments, and incident response procedures are important parts of this decision.
6. How will data and transactions be managed? Extracting a service can make it harder to preserve the atomicity of operations spanning multiple areas. You need to plan what happens if one part of a process succeeds and another fails.
7. Do the benefits justify the total cost? It is worth considering not only implementation costs, but also infrastructure, monitoring, security, testing, maintenance, and skills development.
8. Can we start with a smaller change? Sometimes it is enough to organize modules, extract an asynchronous process, or improve deployment automation. The smallest change that effectively solves the problem is often the best starting point.
The Most Common Mistakes When Choosing an Architecture
The first mistake is equating modernity with the number of services. Microservices are a specific way of organizing a system, not an end in themselves.
The second is designing an architecture based on predictions that have not yet been validated by the product's actual behavior. It is worth preserving room to grow and extracting additional components when a justified need arises.
The third is splitting a system without defining business boundaries. If services are strongly interdependent, the result is a distributed architecture that retains the problems of a monolith while adding network communication costs.
The fourth is overlooking the operational side of the solution. Every new service must be deployed, monitored, secured, and diagnosed. Without appropriate processes, a growing number of components can reduce reliability instead of improving it.
The fifth is trying to solve organizational problems with technology alone. Microservices can support team independence, but they cannot replace a clear division of responsibilities, communication, and good product management.
The sixth is treating a modular monolith as a temporary solution that should be abandoned as soon as possible. A well-designed monolithic application can remain the right choice for many years. There is no need to complicate its architecture without a specific benefit.
Architecture should evolve alongside the product
There is no single architecture that will be best for every application. A small product developed by a team of a few people has different needs than a platform supporting many independent business domains and developed by a dozen or more teams.
A monolith can offer simple deployment and easier operational management. A modular monolith helps organize the application's structure while preserving that simplicity. Microservices, in turn, can provide independent development, deployment, and scaling if the product and organization genuinely need this model.
The most important thing is to identify the problem the company is facing. There is no point in introducing a dozen services just because that is what the architecture of popular platforms looks like. Nor is it worth clinging to a monolith if its limitations are holding back product growth and there are clear benefits to extracting part of the system.
A good architectural decision is not about predicting every future need. It is about building a solution that meets today's requirements without closing off the possibility of making sensible changes in the future.
Before splitting your application into microservices, check whether you really need independent services or simply a better-organized system. This difference can determine not only the architecture but also the costs and pace of product development.
Sources and further reading
-
AWS Well-Architected Framework: Choose how to segment your workload - trade-offs between monolithic and microservices architectures.
-
Microsoft Azure Architecture Center: Microservices architecture - complexity, communication, testing, and data management in distributed systems.
-
microservices.io: How modular can your monolith go? - organizing a monolith around business domains.
Related Web24 articles
-
Your application is 5 years old. Is it still suitable for further development?
-
Why does adding a new feature to an old application cost more and more?
-
Your application works. But can you safely deploy it 100 times a day?
The links above lead to articles covering topics related to modernization, development costs, and the deployment process. They are not a substitute for a separate analysis of the architectural choice.
Enjoy reading.



