Monolithic & Microservices Architecture
Monolithic and Microservices Architecture are two different approaches that can be used to create an application, and each approach has its pros and cons. In this article I explain the main difference between them, and what are some of the benefits and challenges of each architecture.
A Monolith is a software application that typically contains all the code in a single code base. When working with .NET Core, for example, the whole application will be inside of a single solution file, and in a monolithic architecture, the application will connect into a single database. This is a very common approach and generally almost all developers when started to code, work in this kind of architecture. A monolith app is easier to implement and is much less complex than a microservices architecture. This is an example of a Monolithic Architecture:
Note that in this architecture you can also have many layers, but it will be everything inside the same solution and this application will connect to a single database.
Some advantages of a Monolithic Architecture are:
- Simplicity — All the code is in a single solution. If you need to change something, all the code you need is in a single place. If you need to run the app locally, it’s only necessary to run a single application.
- Easy to deploy — It’s easy to deploy because it’s only necessary to deploy a single project. Every time a new feature is added or a bug is fixed, it’s only necessary to deploy a single application.
- Well known — Almost every developer already worked in a monolith application, so this makes it easier to find someone who can easily start to work on the project.
- Easy to debug — It’s easy to debug a monolith because all the code is in a single solution. So when you need to debug, it’s only necessary to run the code and debug without doing extra configurations.
- Easy to test — It’s easy to perform tests in a monolith application since everything is in a single app, so it’s not necessary to configure the communication between other applications, neither check if the other apps are running, and so on. You can also easily implement end-to-end testing by launching the app and testing the UI with some tool like Selenium.
- Easy to monitoring — When a failure/bug occurs, it’s easier to identify where the problem happened, because the code is all in a single project.
Some disadvantages of a Monolithic Architecture
- Can become too big and difficult to maintain — When the project starts, it’s easy to maintain, but through the years the application can become bigger, and this can be difficult to manage. A monolith application works really well for small or medium applications, but when the application is very big and complex, this can become a problem.
- Unavailable time in deploys — When something changes in a monolith and needs to be deployed, the whole application will be unavailable during the deployment.
- Can be difficult to work with big teams — Imagine a scenario where you have a monolith application, and there are many teams working in the same application. In this scenario, there are more space to some problem happens, because every team will be working in the same code, in the same project, this way increases the possibility of some conflicts happened when the code is merged in the repository, or also sometimes when one change is made by a team can affect something that the other team is working.
- Scalability is not flexible — Monoliths can scale, but it’s only possible to scale the full application. If the application receives too many requests in only some specific part of the app, you can not scale only this part, it will be necessary to scale the whole application. Also, it’s not always possible to make use of horizontal scaling, it will be necessary to scale vertically and this is generally more expensive.
- Restrict to the chosen technology — It’s difficult to switch the technologies in a monolith application, so generally, when a monolith is created, it will probably use the same technology for long years.
When should I use Monolithic Architecture?
A Monolithic architecture can be very handy in many scenarios as:
- When you know that the application is not going to be so big
- When you don’t know exactly how the application will be, for example, if the requirements are not well specified, or the domain is something new to you or to your team
- When the project is a proof of concept
- When the team is small or is a new team and the domain is also something new
- When the app will only contain some CRUD operations with a few business rules
In a microservice architecture, you split the application into small services, and these services are independent of each other (loosely coupled), they are technology agnostic and each one has its own database. This kind of architecture brings a lot of advantages, but also comes with many challenges and complexity. Working with microservices also demands more experience and seniority from the development team.
This is an example of a Microservices Architecture:
As shown in the image above, the UI connects with one or more microservices, and these microservices can communicate with each other by synchronous or asynchronous communication. Also each microservice has it’s own database.
It’s also possible to use an API Gateway, which will handle the requests through the microservices, in this case, the UI will connect to this API Gateway and the API Gateway will handle all the requests between the microservices:
How small a microservice should be?
This is a common question when starting to thinking about microservices. There are no rules saying how big or how small a microservice should be, but in many places, you will find that a microservice should be the size of your Bounded Context, and I totally agree with this mindset.
Some advantages of a Microservice Architecture are:
- Flexible scaling —Each microservice can be scaled independently of the other services. This way when a part of the application is receiving many requests, for example, it’s possible to scale only the specific microservice, instead of scale the whole application. Microservices are handy in cases where it’s necessary to have high availability of the application.
- Deploy independently — Because microservices are loosely coupled (they are independent of each other), it’s possible to deploy only one microservice. This way it will not demands that the whole application stop working for a few moments because only a small part of the app will be updated/changed.
- Eliminate the single point of failure — Splitting the application across many small services, eliminates the “single point of failure” in the app.
- Reduces the risk of breaking the app — If a microservice breaks, it will not affect the rest of the application, it will only affect that single microservice, while the other parts of the app will continue working correctly.
- Minimize downtime when a new version is released — When a new version of the microservice is released, the application will not stop working for a while, only the part which uses that specific microservices will be unavailable.
- Can be easier updated — Since a microservice are not, in general, a big application, it’s easier to change something in the code or even update the framework which is used by the microservice.
- Easier to work with multi teams — When working with many teams, each team can be responsible for a specific microservice, or for a group of microservices, separating the responsibility of each part of the project between the teams.
- Easier to understand — A small application is in most of the time easier to understand than a bigger application.
- Easier to expand — It’s easy to expand an application by creating new microservices.
- Independently changeable — When something needs to be changed in a microservice, only the own microservice needs to change, and this will not affect the other microservices.
- Can have different databases — It’s possible to choose a different database for each microservices. In one microservice a relational database can be the best option, and for other microservice, a NoSQL database fits better, and this is possible to achieve when working with microservice. Each team can define which database is the best option for the microservice.
- Technology agnostic — Each microservice can have different technology, one can be done in .NET, other in Java, other in Node, other in Go, and so on. This way each team can define which technology they want to use in each microservice.
- Agility — With microservices it’s very easy to add something new and deploy a new version without having much impact on the whole app.
Disadvantages of Microservice Architecture
Working in a microservice architecture it’s much more complex than working in a monolith application. Of course, a microservices architecture brings a lot of benefits, but also comes with a lot of challenges.
Some challenges of Microservices are:
- Development productivity — Imagine a scenario where there are ten microservices, and you need to implement a new feature in one of them. In some cases, it’s necessary to execute many of them locally (using some specific version) or make use of an environment where the microservices are deployed for development purpose, to be able to access many parts of the application.
- Can be difficult to debug — It’s not so simple to debug an application in a microservices architecture because most of the time it’s necessary to run more than one microservice to debug or perform tests. Imagine that you are implementing a new feature or searching a bug in a microservice, but to test the whole process, it will be necessary to run two other microservices in order to test or search for a bug in the process.
- Communication between the microservices — The communication between the microservices is also something that needs to be taken into account when working with this kind of architecture. There are some microservices that can use synchronous communication (like user authentication for example), but in others, it’s better to use asynchronous communication using some kind of message technology like RabbitMQ, Kafa, Azure Service Bus, or others. This also increases the complexity of the app.
- Handle with errors — The error handles in a microservice architecture is much more complex than in a monolith application. Imagine a scenario where a request will be processed using two or more microservices, if something goes well in the request to the first microservice, but something goes wrong in the request to the second microservice, the operation should be reverted to the previous state.
- Update Shared Data — Imagine a scenario where you have in one Database the table “Customer”. In one microservice (or imagine also a bounded context) like “Sales”, the entity “customer” can have some properties, and in another microservice (or bounded context) as “Support” for example, customer can be something different, with other properties, but some properties can be shared between them like Id, Name, and others. So when some operation in one microservice change some information in the Customer from the Sales, for example, it’s also necessary to update the Customer in the Support, and the other way around. In this situation, you can think about “Saga”, which is a design pattern that can be used to manage data consistency across microservices in distributed transaction scenarios. A saga is a sequence of transactions that updates each service and publishes a message or event to trigger the next transaction step.
- Automated deployment — Different from a monolith application where it’s only necessary to deploy a single app and it’s easy to do it manually when working with microservices the deployment process should be automated because it will be necessary to frequently deploy many applications.
- Identify errors can be difficult — Identify a bug in a microservice architecture, can be much more complex and difficult than in a monolith app, especially when handling asynchronous communication. If you need to debug the app and search for the problem, probably it will be necessary to run more than one microservice locally.
- Monitoring — In order to have traceability, it’s a good approach to have a centralized place where it’s possible to check the logs and monitor all the errors or problems that happen in a microservice. This will make a bit easier to identify where some error/bug occurred in the application. In order to track problems and errors, it’s important to have logs and make use of some monitoring tool like Elmah or others.
When should I use a Microservices Architecture?
A Microservices architecture can be handy in many scenarios as:
- When you know that the application will grow a lot and will be really big
- When you know about the domain of the software and the business rules, or the requirements are well specified and it’s possible to identify that will be a complex application
- When the team is re-written an old application which is big and complex
- When availability and flexible scaling is one of the requirements for the application
- When working with multiple teams and the domain is well known
Each kind of architecture brings some advantages and some disadvantages, and to choose the right architecture for your project it’s good to take into account the points that were explained. Like it’s said, “there is no silver bullet”, so each approach will bring pros and cons.
To decide which architecture you should use in your project, always try to identity some points making some questions as: The application is going to be big and complex? Do I or my team have good knowledge about the domain? Flex scalability and high availability is a requirement to the app? Does my team have experience working with microservices? It’s important to also take into account the knowledge of the team and the complexity of the system. If you answer yes to most of these questions, probably a Microservice architecture can be a good option for your project. But if you know that the application will not be so big, and you don’t really need to have the app 99% of the time available, and the domain rules are not well defined, or the application is not going to be too big or too complex, or the application is only a simple application, a monolith app can be the best option for you.
A monolithic architecture is simpler and easier to implement, but the project can be a bit difficult to maintain when the application grows too much and more developers are working on the project. A microservices architecture allows you to have smaller and separated applications, allowing you to use different technologies in each microservice, allowing flex scalability and making it easier to work with different teams, but this also brings much more complexity to the project. Being aware of all these points can help you to define which architecture will fit better for your application.