Microservices Architecture
By: Cynthia Lee
Tags: Monolithic, Microservices

In recent years, microservices architecture is becoming increasingly popular in the world of software development. Having encountered and worked with it for the first time throughout a recent internship, I am deeply intrigued by how different it is from the more traditional, monolithic architecture styles that I have encountered in school. In this article, I present my understanding and analysis of the microservices architecture and its use cases.

What is Microservices Architecture?

Under the microservices architecture, an application is structured as a group of individual services which are developed, deployed and maintained independently of each other. These services communicate with each other through lightweight, language-agnostic protocols like HTTP, and work together to achieve common goals. This is in contrast with the monolithic architecture, in which the application exists as a single unit with all of its functionalities residing in a single code base.

Comparison between Monolithic and Microservice Architecture (Image from https://rancher.com/blog/2019/microservices-vs-monolithic-architectures)

Some prominent examples of companies who have been shifting towards or adopting the microservices architecture in their applications include Netflix, Amazon and eBay. During a recent job interview at a local e-commerce company, a developer also shared with me how his company’s flagship online shopping application was composed of multiple smaller microservices, each responsible for different purposes such as e-payment, cataloguing items and managing delivery systems. For such large, rapidly growing organisations serving thousands of users daily, the microservice architecture has been proven to bring many benefits which have allowed these companies to gain an edge over their competitors.

Characteristics and Advantages of Loosely-Coupled Microservices

Extending from the Single Responsibility Principle, in which any unit of function in programming (a class, an object, a function or subsystem etc) should have one and only one reason to change — in other words, a single responsibility. Likewise, a microservice should only be responsible for a discrete purpose, fulfilling a set of highly cohesive goals and avoiding overlaps with other microservices. For example, a microservice that handles transactions should not also have to handle logging or notifications. Each microservice is likely to have its own codebase, database, architecture and infrastructure, with its internal implementation, complexity and details hidden from clients. However, the microservices should still be able to communicate with each other through a set of well-defined protocols and APIs.

By decomposing a large monolithic application into several microservices, we can minimize the degree of dependency of the microservices on each other, making them loosely coupled and enabling the following benefits in comparison with monoliths:

Faster and more efficient development and deployment

Instead of a group of developers working on the same, large codebase, the work can be divided, with different teams working on individual microservices in parallel. With a smaller, more easily maintainable codebase and narrower scope to focus on, developers can implement and deploy changes more quickly without affecting the other microservices, minimizing conflicts and improving productivity.

Instead of a group of developers working on the same, large codebase, the work can be divided, with different teams working on individual microservices in parallel. With a smaller, more easily maintainable codebase and narrower scope to focus on, developers can implement and deploy changes more quickly without affecting the other microservices, minimizing conflicts and improving productivity. Higher flexibility

Since each microservice is developed and maintained independently of others, developers have greater autonomy over the choice of the tech stack, technical implementation, and even the integration of different microservices.

Since each microservice is developed and maintained independently of others, developers have greater autonomy over the choice of the tech stack, technical implementation, and even the integration of different microservices. Better fault isolation and recovery

The failure of a single microservice will not cause the entire application to stop functioning, allowing the system as a whole to retain limited functionality through its other microservices. Furthermore, after the failure has been handled, the failed microservice can be independently redeployed, instead of having to redeploy the application as a whole, resulting in a lower likelihood of a failed redeployment and shorter downtime.

The failure of a single microservice will not cause the entire application to stop functioning, allowing the system as a whole to retain limited functionality through its other microservices. Furthermore, after the failure has been handled, the failed microservice can be independently redeployed, instead of having to redeploy the application as a whole, resulting in a lower likelihood of a failed redeployment and shorter downtime. More efficient scaling

As needs change over time, resources can be selectively delegated to the microservices that require it more, instead of having to scale the entire application, preventing inefficient use of resources and reducing costs.

Microservices Architecture Drawbacks and their workarounds

No system is perfect. Although microservices architecture appears to solve many longstanding problems in traditional monolithic architectures, there are some disadvantages to consider.

Higher operational and maintenance complexity

With a large number of microservices that are running in parallel, there is limited observability into the behaviour of the system, making the system as a whole difficult to maintain and debug.

With a large number of microservices that are running in parallel, there is limited observability into the behaviour of the system, making the system as a whole difficult to maintain and debug. More complex communication between microservices

As the number of microservices working together in an application increases, and as these microservices undergo changes, managing how they interact and communicate with each other would become increasingly complex. Additional effort will also be required to handle authentication and ensure that the communication between microservices is secure and efficient, and that latency is handled adequately.

As the number of microservices working together in an application increases, and as these microservices undergo changes, managing how they interact and communicate with each other would become increasingly complex. Additional effort will also be required to handle authentication and ensure that the communication between microservices is secure and efficient, and that latency is handled adequately. More difficult integration testing

While unit testing for each microservice is straightforward, it is more challenging to set up an environment to test multiple distributed microservices together as a system.

To facilitate the transition towards microservice architectures, as well as to tackle some of the aforementioned inherent issues faced in implementation, there are various well-established and prominent tools and design patterns available.

API Gateway Pattern to Improve Communication

Diagram comparing the architecture of the 2 communication patterns (self-created)

Similar to the Facade Pattern, having an API gateway as an intermediary layer between the client and the various microservices allows for the complex interactions between them to be handled more easily and efficiently. Instead of each client handling multiple endpoints from various microservices, or having to change together with microservice APIs, the API gateway provides clients with a single point of access to the microservices they need through its own API. The API gateway layer can simultaneously handle the routing of requests, conversion of protocols between the client and microservice, aggregation of data, and even additional functionalities such as authentication and load-balancing. Although the implementation of and maintenance of this layer would require dedicated effort, potentially becoming a development bottleneck, it could significantly simplify client-microservice communications and reduce overhead and latency.

Tools to Improve Deployment and Maintenance

With container platforms like Docker, microservices can be encapsulated into lightweight containers and run in an isolated environment independent of their host, making it an excellent tool for managing and deploying microservices.

In a large application with hundreds of microservices running alongside each other, monitoring is crucial to ensure that the system is behaving as expected, and facilitate troubleshooting when issues are encountered (such as the failure of a microservice on which other services are dependent). Tools such as Prometheus can be used to observe and monitor individual microservices, providing deeper insight into the health and behaviours of the application as a whole. For more information about Prometheus, I have also written another short article about its features and use cases. You may access the post through this link.

When to use Microservice Architecture

Adopting the microservices architecture is not a one-size-fits-all solution. It is crucial to examine the situations in which the benefits would outweigh the costs. Apart from the nature and requirements of the application, it is also important to consider external factors, including organisational and team structure, availability of resources and manpower.

In general, microservices architecture would be suitable in the following contexts:

A complex and rapidly-changing application with multiple parts.

Parts of the application need to be scaled on-demand and/or need to be highly reusable.

Multiple smaller development teams, each with a specialised area of focus.

More resources for the hosting, deployment and management of multiple microservices.

References