Imagine a scenario, where the President of a country is the only person who handles law, finance, international defense, education, transport and all other things?
Imagine, a CEO of a big fortune 500 company like Amazon, who takes care of the of technology, human resources, employment, marketing, operations and finance?
Sounds crazy, right? If only, there was a better way….
The “better way” is obvious, a President has a cabinet of ministers who handle their own domain and have completely responsibility of that area. A fortune 500 company has executives who head their respective departments. In both these cases, there are different people — who have expertise and specialization in their own domain. If they need help from another department, they simply seek help from an executive of another department.
Makes sense, right? This is the underlying concept of Microservices…
Confused? Read on…
Consider a large scale eCommerce application such as Amazon. Amazon has various underlying operations like authentication, profile management, product management, browse and shopping experience, inventory management, payments, order management, frontend and such. Let’s say that all these services are packed into one HUGE application. Now, this application is responsible for all the operations executed as a result of user interaction, just like the CEO who does everything! This kind of application architecture is called Monolithic architecture.
Breakdown of Monolithic architecture
- In the above diagram, an end user is connected to the internet and accesses an application. The request from the client (browser) navigates to the hosted website through the internet.
- A Load Balancer is usually configured as a first point of contact between the incoming request and the physical servers (in the diagram, one physical server is shown. Depending on the load of an application, more servers can be added and registered with the Load Balancer).
- A load balancer makes sure that the incoming requests (possibly coming from thousands or millions of users) are spread evenly among the physical servers.
- Each physical machine has single or multiple instances of the application which actually processes the user requests.
- The components and services inside the application (like Inventory, Profile Management, Browsing, Shopping, Payment etc.) are tightly connected to each other. The interact with each other and generate a response to that request and send it back to the client.
What is wrong with a Monolithic architecture?
While the rights and wrongs of choosing an architecture is usually requirement based, but there are some general concerns while developing into this architecture:
- Scalability: The biggest concern with a monolithic architecture is scalability. A huge application having everything compiled into a single chunk is difficult to scale. If new servers need to be added, the whole application has to be deployed to every server.
- Large Codebase: Requires maintenance of a large codebase. This may not be a concern in early stages, but as the application features increase, changes have to be made in different services and the frontend.
- Small Change, Big deployment: For the smallest changes, the whole code has to be compiled, encapsuled and deployed again on all servers. Even if it is a small layout change, such as shifting a button from left to right!
- Tightly coupled: The whole application is tightly intertwined. All the services and components MUST be available for the application to be compiled. A re-deployment is necessary even if one component does not directly affect another (for example, refactoring the UI into a new layout would require building and deployment of the backend services, although there are no changes in the backend)
The solution? Microservices!
A microservices architecture breaks down an application into loosely-coupled logically separate services, each of which holds a “Single Responsibility”. These services interact with each other with the help of synchronous (HTTP REST APIs) and asynchronous (Kafka, RabbitMQ) communication.
In the example of eCommerce application we discussed above, it could be divided into multiple microservices, each having their own life cycle. For example, a separate module for each of the services of Profile Management, Inventory Management, Cart, Payment etc. could be deployed.
When a user adds an item to cart and completes the payment, an asynchronous push message (via Kafka or RabbitMQ) can be sent to the Inventory Management microservice, to reduce the inventory items in the database. Similarly, when a user goes to see previous orders in the profile, a REST API call to Order Management microservices can be made to fetch the order history and the latest state of the orders (processing, in delivery, delivered etc.)
Breakdown of Microservices Architecture
The above diagram shows a very basic representation of an eCommerce based microservices application. Let’s see how the flow works:
- A client can access the application by a native mobile app (android or iOS) which interacts with the application through the internet.
- A client can access the application by a browser (web app) on a computer or a mobile phone via the internet.
- Certain features of the application can also be accessed through an Admin Console which is used by the company employees to add new products, update existing products & updating inventory of products (which recently came in stock).
- The request from the client (from either [1], [2] or [3]) goes to an API Gateway, which figures out the microservice to be called based on the incoming request.
- If the request is coming from an Admin Console to update the inventory of a product (for new stock of that item), then the call goes to the Inventory microservice to update the stock of a current product. The update inventory call (for new stock) is usually done with an authentication & authorization mechanism, so that only authorized users can update the inventory (and not the end users).
- If a Native Mobile App or a Web App is accessing the application for browsing the products, then the API Gateway passes on the incoming request to the Shopping & Browsing microservice, which can hold a lot of different APIs like getting the products in a category (like Men’s Trousers) OR getting the detail (like size, material, color, description, washing instructions, reviews) and pricing of a single product (e.g. Lacoste White Sneakers).
- There could be other microservices in the application which perform other tasks. For example, a Cart Operations microservice which has APIs to add/remove a product to/from the cart, to update the quantity of the items in the cart. Or a Checkout Operations microservice which takes the shipping address, billing address, delivery instructions, gifting wrap instructions to complete a checkout process.
- Ultimately, when the user passes through these microservices, the user pays for their items. The Payments microservice is solely responsible for handling payments through multiple means like Credit Card, Bank Account Debit, Paypal and such.
- The Payments service, in turn may pass the request to other microservices on successful order like sending an asynchronous KAFKA message to the Email microservice which sends an email to the end user with a summary of the order AND/OR to the Inventory microservice to reduce the inventory of purchased item(s) in the order.
There may be cases where one microservice receives a lot more load than the other microservices. For example, a product browsing microservice will receive much more requests compared to an Account Management microservice. In such a case, multiple servers for the Browsing & Shopping microservice can be setup which can managed by their very own Load Balancer.
Advantages of using Microservices architecture
- Modular Changes: Changes in one service does not require deployment of all the services. For example, once a user successfully places an order, an email has to be sent to the regional delivery manager. This change requires changes only in the Order Management microservice, while the other services remain unchanged.
- Easy to maintain: Since each microservice is decoupled from another and has a different codebase, different teams can work on their individual modules without impacting other modules. One of the biggest examples of such a case is the separation of backend and frontend. Frontend changes which do not revolve around the APIs (like layout changes, CSS changes, javascript changes) can be deployed separately. In fact, frontend releases can be entirely decoupled from backend releases in many cases.
- Scalability: Some microservices can be heavily used (like product browsing API), while other microservices are less frequently used (e.g. Profile Management). These heavy microservices can be easily upscaled, while the other ones can run on low resources. Thus, optimizing the cost of the overall architecture.
- Fault Isolation: If one of the services is down, the whole application does not go down. For example, if inventory management is down, the whole application does not have to be unavailable. Instead, a robust and graceful error message can be shown in such a case.
Concerns in using Microservices Architecture
So far, it sounds great to employ microservices architecture for a new application, but it also comes at a cost. In some cases, it may be a pain to use this paradigm.
- Latency: Since there are different microservices which interact with each other, there may be delays due to communication overhead.
- Transaction Management: Transaction management across different services is a pain. Although there are strategies to manage that, it causes additional latency.
- Integration Testing: Integration testing becomes difficult and hard to manage. Extra configuration needed to setup testing infrastructure.
- Server Resources: Since each microservice requires its own container, it adds up additional cost.
When to use Monolithic architecture?
A monolithic architecture is recommended, when there is limited time and resources to build an application. A monolithic application can significantly reduce the development time and can be cost-optimized in primitive stages of a product OR a POC project OR an MVP (Minimum Viable Product).