A microservices architecture addresses challenges by breaking the application down into smaller components or services.
Less than 10 years after the term was first coined, microservices architectures are becoming increasingly mainstream, but how do they compare to a traditional monolithic design? In understanding the reasons for and against each architectural approach, it helps to look at how applications, and specifically their codebases, grow over time.
Monolithic vs Microservices Pros and Cons
With a monolithic architecture, there are very few moving parts; most of the features are delivered by the same codebase, with stateful objects stored in a single database. This approach works well for simple applications being developed by a small team. Everyone involved understands how every part of the application works, and with few dependencies, the team can test and deploy changes quickly. For start-ups trying to get a new product to market quickly, the monolithic approach offers low overheads and short development cycles, enabling them to move quickly.
However, as the application becomes more complex and the codebase grows, development slows down and the time between releases increases. Why does this happen? As more features and functionality are added, the codebase gets larger, which makes it harder to keep clean code abstractions and maintain accurate documentation. When changes are required, its difficult to isolate experiments and avoid impacting the rest of the application, stifling innovation. A larger system requires the development team to grow too, but new starters face a steep learning curve as they attempt to get to grips with the entire system and anticipate the impact their changes will have. All of these factors slow down development and release cycles, causing frustration both for management and engineers, which in turn undermines morale the last thing you want when your success depends on the creativity and commitment of the team.
How do microservices help?
A microservices architecture addresses these challenges by breaking the application down into smaller components or services. Each component is responsible for a single business function (hence microservice) and communicates with other services over APIs and messaging protocols. Each development team is responsible for an individual service, and because services communicate with each other via APIs, developers dont need to understand the intricacies of other services just the interfaces they expose. Teams can develop and deploy changes independently, and new members have a much gentler learning curve, enabling them to be productive much sooner.
Pros and cons of a monolithic approach
Managing all your application logic in a single process has a number of advantages, particularly for simple applications developed by a small team.
Having been the norm for many years, most IDEs support a monolithic architecture by allowing you to run and test your entire application with a single click.
Cross-cutting concerns, such as security, rate-limiting and monitoring, can all be handled centrally for the entire application.
As there are very few moving parts, its relatively easy to run end-to-end tests.
For smaller codebases, a monolith is simpler to manage and deploy, and the application can be scaled horizontally behind a load balancer.
However, some of these benefits turn into disadvantages as an application becomes more complex and the codebase grows. Weve already mentioned the difficulties of maintaining good code practices and growing a team for a large, monolithic system. In addition:
The only way to scale a monolith is to replicate the entire application, which isnt always efficient.
Developers are constrained by the languages and frameworks chosen at the outset.
Testing and release, even of a minor change or bug fix, requires the entire application to be built and deployed. As the codebase grows, this becomes increasingly time-consuming.
Failure of any part of the application can take down the entire system.
Pros and cons of a microservice approach
By splitting complex applications into their individual components, each focused on a single business function, a microservices architecture offers multiple advantages over a monolithic design:
Microservices are loosely coupled and communicate via APIs, which provide an abstraction layer from the underlying logic. As a result, teams can work in parallel, developing and testing their changes independently of the rest of the system and enabling faster, iterative development cycles.
As each service is independent, teams can choose the language and framework best suited to their needs. This also gives teams space to innovate and experiment without impacting the rest of the system.
Responsibility for a specific, well-defined microservice results in a gentler learning curve for new hires, who can be productive much sooner.
Because each microservice can be deployed independently, they can be scaled individually according to the load on each service. Spinning up additional instances of individual services is more efficient than replicating the entire system and allows organizations to leverage the benefits of elastic cloud-based infrastructure.
If an instance of a microservice is compromised or fails, it can be isolated and taken offline without impacting the rest of the system. Together with the horizontal scalability of individual microservices, this makes for a more robust and resilient system.
While a microservice architecture offers many benefits, there are some complexities associated with a distributed system:
A microservice architecture involves more moving parts than a monolithic design, which increases the complexity at the outset. For simple applications managed by small teams, a microservice approach may be overkill.
Exposing individual microservices to client apps can make it difficult to interact with the system, as client developers have to understand how to route requests to the dozens or hundreds of services that make up the system. This can be addressed by adding an API gateway as an abstraction layer between the backend services client apps to route requests and aggregate responses.
Cross-cutting concerns, such as security, authentication and monitoring, need to be applied to each microservice, and the functionality must be replicated for each language used in the system. However, managing these functions centrally from an API gateway avoids this problem and ensures a consistent approach.
Testing a microservice-based system effectively requires multiple layers of automated testing to manage dependencies and enable services to be deployed independently. While this may sound like a burden, automated tests are a worthwhile investment that enable continuous delivery of any system, whether its monolithic or microservices.