If you are starting to build a new application or you are working on an existing one you may be wondering whether you should be building a monolith or a microservice application.
Each architecture has its own pros and cons but hopefully, by the end of this article, you will have a good understanding of what the differences are and which one you should be using for your project.
Before microservices were a thing everyone was building monoliths. A monolith is an application where everything works as one piece of code.
It is usually the starting point for any new application. Whether it be an API or a Frontend. You end up with one executable with all the code in one repository.
Your application is then developed, deployed and also scaled as a single component.
If you are working in a team on the same application, then you need to work together to make sure you don’t step on each other’s toes.
Anyone who has ever worked on a big code change in one sitting will know the pain of having to merge everyone else’s changes into your code.
Before we cover the problems with monoliths, they do have a number of key advantages that are important to go over.
Monoliths are easy to develop as all the code is in one place. You don’t need to worry about maintaining different repositories, you only have one application to run and test.
If you are working on a small application then this can make a big difference in the time it takes to develop.
Most applications start as monoliths simply because they are quicker to get to market.
Monoliths are easy to deploy, after all, you only have one application to worry about.
On top of that, you only have one CI/CD pipeline to worry about and infrastructure for one application to deal with.
If you only have one application then there is only one place to look for a bug.
The real benefit comes when you are debugging your application locally as you only have one application to step through. If you need to debug multiple applications at once it can become a bit of a nightmare especially if they are all running on the same port.
I won’t go as far as to say that monoliths are more performant than microservices, however, with a monolith you don’t need to worry too much about latency.
If you only have one application then there is a negligible cost between parts of your code calling each other. Which we will see with microservices gets a bit more complex.
So if monoliths are so great why bother with microservices at all? Despite the benefits, monoliths do come with some big issues that often cause teams to move to microservices further down the line.
Every developer starts with good intentions and applies clean code practices to make their application easy to maintain.
However, even the best-designed application is going to look like a giant mess once it reaches a certain size.
If it takes a month to onboard a new developer and explain what all the different parts of your application are doing, then you may need to look at other options.
If your application has lots of different features then there is going to be a lot that you need to test before each release.
As your application is one big component then even the smallest change requires you to release the whole application each time.
Depending on the application and your CI/CD pipeline, deployments can take a long time, especially if you need to worry about switching traffic between green and blue APIs and monitoring the logs.
If a part of your application is getting a lot of traffic then you have little choice but to scale the whole application. This might mean spinning up more instances of your API or beefing up the CPU or memory on your server.
Either way, it is going to be expensive because your application is so large.
Now that you have seen the disadvantages of monoliths, especially as your application gets bigger, we can look at the solution, which is microservices.
With microservices, we take your big monolithic application and break it down into different components. Each component has a single responsibility and is usually in charge of a single business functionality.
If you take Netflix for example, they might have one component in charge of search, another in charge of streaming the videos and another responsible for the recommendations.
Each service is self-contained and independent from all the others. They have their own infrastructure and their own database for example.
Each microservice is its own application that has its own version and is released independently.
If you split your application up then how do all the microservices talk to each other?
There are 3 main ways that your microservices can communicate.
Each microservice can have its own endpoint that the other microservices can call. This should be used when you need synchronous communication between your components.
As with all HTTP requests, this is going to introduce a bit of latency in your application that you need to take into account. However, you can use things like gRPC to speed up communication.
If what your service is doing can be done asynchronously then you are better off using a message broker.
For example, if you are application needs to send an email, it isn’t usually necessary that the application waits for a response from the SMTP server.
It is much easier to put the message that you want to send in a queue and have your application carry on with what it was doing. A microservice listening to that message queue can then pick up the message and send the email.
You can use technologies such as RabbitMQ or AWS SQS to communicate with your asynchronous microservices.
The last option is to use what is called a service mesh. A service mesh handles all of the communication between microservices as well as helps deal with the reliability and discoverability of your services.
Obviously, no one would use microservices if they didn’t have a number of key benefits over the monolithic design.
A well-designed microservice is only responsible for one thing and as a result, they are a lot easier to maintain. In many cases once written you may not have a need to touch a microservice at all, it will just carry on running without you needing to do anything.
Compare that to a monolith where you are redeploying the application with every change and therefore each time adding in the potential for something else to go wrong in other areas of your application.
As you only need to deploy a single microservice when something has changed the other microservices are unaffected.
As a result, your application becomes more reliable as there is less chance that the whole system is going to come crashing down if there is an issue with one of the components.
This is why if you look at outages at companies such as GitHub, it is rarely everything that goes down. It might be webhooks that stop working or you are unable to commit but it is usually limited to specific functionalities in the application.
This can be both a blessing and a curse.
Some languages are more suited to particular tasks compared to others. Take machine learning for example, you are much better off writing a machine learning algorithm in Python compared to C#.
However, if teams have completely free rein over which technologies they use you can end up having to reinvent the wheel every time you want to create a new microservice. It is a lot more efficient if all the services are using the same tech stack.
Being able to have separate teams working on the same application independently from each other is a big selling point of microservice architecture.
It is rare that all areas of your application are going to get the same number of requests. There are going to be some functions of your application that are going to be used more by your users compared to others.
If your application is a monolith and one part of your application is getting the majority of the requests you have no choice but to scale up the whole application.
However, If you have a microservice architecture you can scale up just the microservice that needs it and therefore save yourself money in the process.
If you want to get to the point where you are deploying multiple times per day then you need to make sure that each change can be easily tested and automated.
If you have a monolith then this is very difficult to do as even the smallest change could have an impact on the whole application. However, with microservices, your change is only limited to one microservice which can be deployed and tested independently.
It is not all good news with microservices, there are a number of disadvantages that you need to be aware of if you are thinking of using microservice architecture in your next project.
If your microservice is highly dependent on other microservices to work, then it can be a pain to get everything running on your computer when you need to do development.
You can either resort to mocking out all the other endpoints that your application needs or spin up docker containers with all the microservices you need to call.
It can be done but it does make things harder than just running a single application.
When problems do occur with your application in production it can be harder to work out where the issue is.
You no longer have just one application to deal with you now have several and you will need to look at each one to see where the issue originated.
It is important that you have good monitoring in place for all of your microservices so you know when a service is down or having problems. This is where using a service mesh can come in handy.
Microservices can come with higher infrastructure costs as you need to be able to host all of the different services.
If your application is large and you have an uneven distribution of requests then it is usually cheaper than scaling up a monolith but for smaller applications, you aren’t likely to see those benefits.
Microservice architecture does increase the complexity of your system in a lot of different areas.
It makes your infrastructure more complex, your CI/CD pipeline needs to cope with deploying multiple services and you now have to deal with the communication between all the different components.
On top of that, you need to have good monitoring in place so you know if one of your microservices is having issues.
This might not seem like a big deal when you only have a handful of microservices but once you have hundreds it can become an operational nightmare.
We have gone over the pros and cons of each so which one should you be using for your project?
If you are starting out with a new application that isn’t going into an existing infrastructure then start with a monolith.
There is no point in trying to solve scaling problems that you don’t even have yet. If you are building a startup, then your main concern should be getting to the market as quickly as possible.
You can deal with the scaling problems when you actually have them.
Most applications, even Netflix started out as a monolith. It was only when they needed to scale that they split up everything into microservices.
There is one scenario though where a new application should start as microservices and that is if you are hooking into an existing workflow.
If you know that your application is going to be getting a million requests from day one then you need to plan accordingly. In that case, it is usually better to start with a microservice architecture or at least a semi-monolith with key components broken down into their own services.
Once your application is live then you can look to see if it makes sense to break down your semi-monolith into more microservices.
If you are not sure what architecture to use for your microservices then you should take a look at hexagonal architecture.