It’s the classic decision between old school and new school; the tried, tested and trusted option or the new kid on the block. The world’s leading tech companies – including Netflix, Uber and Amazon – have pondered this. For example, way back in 2008, a single mistake caused massive data corruption and led to several days of downtime on Netflix’s platforms. That’s when Netflix architects decided to move their entire application from monolithic architecture to AWS cloud-based microservices. The main goal of this migration was to improve availability, scalability and speed – they wanted Netflix to be available around the clock, work fast and scale easily.
So, let’s dive into the pros and cons of monolithic architecture and microservices. These insights will be useful while you’re planning your own business application architecture.
Before we can do a fair comparison between monolithic and microservices, we need to understand what modern monoliths really look like compared to the classic version. As our hosting architecture evolved over time, so did the monolith. It’s no longer a machine that you ordered sitting at a hosting company’s data centre, with the specifications you requested three months ago, dedicated to this one purpose. No longer do you have to shut down the entire system so you can upgrade the memory or some other component and then turn it back on and hope it all starts up correctly. This process is very similar to upgrading your home computer, and we’ve come a long way since then. We now have vertical scaling, which allows you to add more power to the machine.
A more accurate description of a modern monolithic system would be a dockerised container running multiple nodes and each node having multiple threads. It’s hosted behind at least a load balancer with some autoscaling configured to scale the system when the load on the system justifies it. This is known as horizontal scaling, which allow you to add more nodes to the auto-scaling group. You’ll still be limited by the amount of power each node has, but this can be configured beforehand and changed when required.
Normally there will be a database attached to either of the solutions; whether that is a classic relational database or a NoSQL database will be based on your application’s requirements.
At this point, you’ll have grasped that classic monoliths only scale horizontally and have to be shut down to scale, whereas modern monoliths scale vertically by adding more nodes – but can also scale vertically by making nodes stronger, and this can be done without shutting down the system. Let’s quickly go over what we mean by microservice architecture before we compare it with modern monolithic architecture and look into some of the benefits and drawbacks of each solution.
A typical AWS serverless microservice system would normally consist of individual lambda functions, each with a single purpose that does not affect any of the other functions it is grouped with. Grouping these functions is done with frameworks like CDK or Serverless, and then transcoded to CloudFormation templates. Each function can be accessed via different services; for example API Gateway, Function URLs, scheduled executions and event listeners. In its execution, each function could make use of numerous other components or services – such as SQS, SES, S3 Buckets and DynamoDB, to name a few.
For the purpose of this comparison, we’re going to exclude caching services like CloudFront, Memcached and CDNs. They’re normally critical for a successful architecture to speed up the end-user experience, but this is not our main focus here.
Let’s look into four aspects of applications and see how the two architectures compare:
Most applications comprise a few key elements:
With monoliths, this initial development is straightforward, whether you set it up directly on your local machine or use a service like Docker Compose to configure your local environment. It’s a basic configuration and almost all services used in your application can be simulated on your local environment and configured within a couple of hours.
With AWS serverless microservices, this becomes a little more tricky. Many of these services can only be in the cloud and although a lot of progress is being made to simulate these services on your local, it’s still not as easy as with monoliths and also not 100% accurate. Your local environment is not exactly like the cloud and once you have a piece of your application working on your local, you always have to make sure it will actually work as expected in the cloud.
Over time, applications evolve, new features need to be added, some features need to be deprecated, and phase two of the application might be in the works. The original team that worked on the project is not always available anymore. Even with a well-documented system, everyone has their own approach to development, no matter the language or architecture in use.
Monoliths are normally built-in frameworks that have been around for a couple of years at least and with big community support – like Laravel or RoR. Even though there’s still a lot open for interpretation when it comes to these frameworks, there is a certain standard that makes it easier for developers to carry on with someone else’s work. On the other hand, you always run the risk of not knowing every aspect of the application, and changing something small at a certain place of the application has an effect on something else you were unaware of. Then you only discover this after the application has been in production for a couple of months.
Microservice, although technically new when compared with monoliths, also has some frameworks that are starting to form a standard – not as much as some of the monolithic frameworks, and there’s a lot more wiggle room, but it’s getting there. When making changes to an existing application, it’s a lot safer than with monoliths, as your changes are limited to the service you’re working on and should not affect any of the other services.
Tracking down a bug and correcting it makes up a very large percentage of a developer’s day. It gets even trickier if you weren’t the original author of the application. It’s critical to have access to the correct logs and be able to simulate the bug in a non-production environment that is exactly the same as production, where additional debugging can be done.
Monoliths are easy here, as there is normally an existing staging environment running with less resources but the same architecture. It would already have a raised debug level. And if you can copy the production data over to the staging environment, it’ll be easy to track down. Debug logs are also normally in one, maybe two, different locations for the entire application. They can be scanned and issues tracked down and resolved.
With microservices, this can become a bit tricky. Each service will have its own log and even if all the logs could be grouped in a single location, scanning through all of them individually isn’t easy. Additional services can be used for debugging on microservices – like AWS’s X-ray – and once configured, they will speed up the debugging process.
It’s been over a decade since microservice architecture has been in the market. It has grown immensely and there’s no sign this will stop anytime soon. Monoliths are also evolving, frameworks are improving, languages release new updates all the time with great features, and community support continues to grow.
Monoliths are still very popular and will be around for a long time, possibly due to current skills in the market supporting legacy applications. We all know how reluctant businesses are to spend money on rewrites that will, in their eyes, deliver the same service.
Microservice applications are on the incline, and most new applications being built seem to be leaning towards microservice architecture for a number of reasons: new developers starting out seem to also be favouring microservices technology. It’s the new kid on the block who everyone wants to be friends with.
Despite having a logically modular architecture (MVC), the application is packaged and deployed as a monolith.
The entire stack is split up into independently deployable modules; each service covers its own scope and can be updated, deployed, and scaled independently.
Migrating to microservice architecture doesn’t mean you have to replace everything at once or even that you have to migrate everything. Follow the strangler pattern, replacing piece by piece, or even make use of a hybrid architecture – whatever suits your application’s requirements.