This tutorial is about the architectural pattern of Microservices. We will learn how the microservices architecture pattern evolved, what are the benefits of microservices architecture pattern and an overview of the evolution process of microservices architecture.
We will also understand some implementation details of microservices. These details will include the access methods, communication patterns and an introduction to containers.
History of Architecture Patterns in Software
To learn about the microservices architecture, let’s take a peek into the history of various patterns of software architecture. We need to understand why did they evolve in progression over time and what were the conditions that were conducive for each of those patterns!
Only then will we be able to understand the reasons of why Microservices architecture is the most suitable one for modern software.
Monolithic Architecture of 1980s
When computing became popular, we used to have monolithic applications on mainframe computers. I remember the days back in the 80s when I used to work in a big corporate organization, where we had one of the most powerful computers of those days. It was a mainframe computer. Despite being a powerful computer, I had to wait for hours to receive the output of a program I was running. If the program had a bug, I would come to know about it only after a couple of hours, make the corrections and wait for a couple of more hours before getting the correct output.
In such applications, everything including the hardware, was integrated. The user interface, business logic, utility programs and even the database was totally integrated into the application itself.
The main issue was the scarcity of resources.
Since the entire hardware and software was shared by many users and background processes, it was very inefficient!
After spending a few years in the industry, I got introduced to a new architecture at that time. It was called the “client-server” architecture.
In this architecture, we could have a nice GUI to run an application which resided on our own machine; however, the main computing was done on a server somewhere in the main office of our organization.
Although this architecture was much more efficient compared to the mainframe computer and the monolithic applications that we were used to, it still was not that fast when compared to the earlier software. I remember sometimes we used to take a few minutes to get the output from the server, before it could reach the client application.
An irritant about this architecture was that it required us to install the client application on each and every user’s machine. And if any changes needed to be made in the client application, then we had to start the installation process on each users’ machine all over again. Sometimes, the only way to find out the application is no longer working on a client machine would be when the users started complaining about it.
Ever popular browser-based web applications
The Client-Server architecture continued to be popular for a few years but the industry was always on the lookout for something simpler – an architecture where we would not need to install anything on a client machine. This gave rise to the architecture where the client part of the application was available in a browser.
A browser happened to be a generic client. There was no need to install anything on the client because most of the users were using the browser which was already installed along with the operating system. In some cases, users installed their preferred browser and used it for accessing all the applications.
The actual application used to run on servers which was called the web server. Web Servers could be in the customer’s own network or somewhere on the Internet. For higher scalability, we could form a web farm of multiple web servers which allowed us to serve the same application to hundreds and thousands of users at the same time. This architecture and the applications which were created at that time, survived for many years, and in a way, it still does.
Service Oriented Architecture (SOA)
It was only because of the modern needs of having an application available on different devices including a mobile phone – that the need for a new architecture was felt.
This new architecture was called service-oriented architecture where server-side application was split into components that were called services.
In SOA, Services made calls to other services for consuming them. These calls were based either on certain proprietary protocols or other generic binary protocols, like SOAP. One of the benefits of this architecture was easy scalability of the entire application to hundreds of thousands of users, within a short period of time. An entire application made up of multiple services, could be replicated on multiple machines. In this way, we could support an enormous number of users.
Although this architecture was very popular, it still failed to address some of the critical issues posed by modern applications.
Issues in SOA that gave rise to the evolution of Microservices
First of all, since SOA used binary protocols and sometimes proprietary ones too, it was difficult to communicate and integrate all the components created by different teams.
An even bigger issue was related to the cost of replication of the entire application for dynamic scaling, and the efficiency with which it could be achieved in a small amount of time when the application received a sudden increase in load.
Slowly this architecture evolved into the Microservices architecture. In fact, some people call microservices as the service-oriented architecture, done right.
Microservices architecture is based upon shared protocols like HTTP, message queuing and the concept of REST API.
Another difference between the service-oriented architecture (SOA) and the microservices architecture is the principle of design on which the services are created.
An initial generation of SOA apps was where the services replaced the components of monolithic applications keeping the layers and tiers of the architecture, unchanged.
In the Microservices architecture, each service is designed to fulfil and implement one of the subdomains in the domain of the application. For example, in an E-Commerce application, instead of creating services based upon layers and tiers in which they exist, we create the services on the subdomains within the domain space of e-commerce like the Shopping Cart, Catalogue, Shopper Management and the Order processing etc.
As we can imagine, not each part of the application is equally loaded. The highest demand is for the part of the application which is related to the Shopping Cart and indirectly the catalogue.
Other parts of the application like the Seller’s Center or User Registration are not so much in demand and do not put as much high demands on the hardware as much as the Shopping Cart part of the application does.
So, when we need to scale out the application, that is to create higher number of instances of the service to cater to the high demand on the popular parts of the application, we need a greater number of instances of the services which are in high demand as compared to the number of instances of the services which are in less demand.
Since we have independent services for each subdomain, we can decide and define the number of instances of each service which will be running to cater to a specific load condition.
We can even let the load conditions be indeterministic. If the load conditions force us to increase the scale of the application, then we know the proportion in which the number of instances of each service are to be increased because we know the relative demand that each service is going to face in higher load conditions. This allows us to have only a minimum number of instances of each service running, which are just sufficient for the load condition.
Designing the Logical Structure of Microservices
Let us now take a look at the logical structure of microservices.
Each microservice is designed to be logically independent and isolated from the other microservices in the same application.
The entire code of that microservice and the components built on top of that code, are within the microservice itself. So much so, that even the ownership of the data which is required by that microservice, is with the microservice itself.
This type of architecture has an added advantage – the inter process communication, even within the application, is very less.
It is implicit in the design and the structure of microservices that one microservice cannot and should not access the data owned by the other microservice, directly.
We come across the concept of eventual consistency which means that the data will be in a consistent state at certain point of time in the future, which may not be immediately when a transaction happens. This consistency will be achieved by making the inter microservices calls and allowing the data owner microservice to take care of the database changes which are needed to be done.
Benefits of Microservices architecture:
1. The first and the biggest advantage of this architecture is the ability to independently scale each service as per the demand on that service. If the shopping cart subsystem is under high demand, there is no need to scale up the subsystem of the seller’s center along with it. This is much more cost-effective compared to scaling up the entire application in response to the increase in the demand for just one subsystem of the application.
2. Another benefit of the microservices architecture is the ability to update each service independent of the other services. It is expected that each service is loosely-coupled with the other services so that a change in one service does not necessitate a change in other services. This makes software maintenance much easier compared to earlier architectures.
3. Since the required hardware resources used are minimum for running each service independently, the overall cost of the entire application is much lower even when compared to the service-oriented architecture.
4. The development of each service and then the subsequent maintenance of that service will be done by a separate team which is proficient in that subdomain. This improves the efficiency of that team and the overall productivity of the entire application development.
It is crucial to design the microservices in line with subdomains of the applications’ major domain.
For such a design to be created, the concept of bounded context is used. The Subdomain of the application define the entities in its own context which are bound to that subdomain. Even though the entity may be similar or exactly the same, in a different context, it may be called differently and it may have different attributes in that subdomain.
For example, in an E-commerce application, the sub domain of the user management may treat an entity called ‘user’ in its context. May be, the same user entity in the Shopping Cart sub-domain is called as a ‘buyer’. The same entity is possibly called as a ‘customer’ in the order processing subsystem and called ‘vendor’ in the seller center.
In the bounded context of user registration, the entity named user has the attributes of the name, address and email address as the attributes under focus, whereas in the order processing subsystem which has ‘customer’ entity, the focus is on the payment details and loyalty code of that customer. The bounded context is used to focus on the different attributes of the same entity and to store the data of that entity in different databases in different servers, or at least in different tables that are owned for editing by the respective microservice.
So, we come to the question – What is the relation between the bounded context and a microservice?
It is desirable to create one microservice for each bounded context in the domain of the application. While designing microservices, it is first necessary to define the subdomains within the domain of the application. As a next step, create the bounded context for each of the subdomain. Once the bounded contexts are defined, we can design microservices for each of the bounded contexts.
Accessing Microservices from clients
Once microservices are logically defined, we can now talk about how the client application will access these microservices.
The simplest way of accessing a microservice is direct client-to-microservice communication. This may use a simple HTTP request response paradigm. This type of communication has no issue when there are a smaller number of microservices in the application.
However, as the number of microservices increase and the number of clients also increase, the necessity of many to many types of connections to be created will arise and overwhelm the communication channels with this chattiness. As a result, the efficiency of the application will reduce.
This type of communication is not possible in a non-trivial application which has many microservices catering to different sub-domains within the application. In such conditions, it is suggested to use communication through an API gateway.
This gateway can be a microservice itself which routes the communication from the clients to the individual microservices intended to be used by the client.
The API gateway may also be a dedicated service offered by the Cloud Service Provider as Platform -as-a-service (PaaS). The API gateway may also actively process the communication from the client, working as Man-in-the-Middle, so that the message received by the business microservice is in the acceptable format, irrespective of the format sent by the client.
For example, the client may send an HTTP request but the business microservice may expect a message in the queue. The API gateway will convert that HTTP request into a message which is put in the queue. While adding the API gateway, care should be taken that the API gateway itself does not become a bottleneck in the application. In that case, it is desirable to create multiple API gateways for each logical subdomain which will give access to different microservices to the client.
Communication between the Microservices
As I mentioned earlier, it is desirable to keep the communication between the microservices to the minimum but it cannot be totally avoided. For example, it may be necessary to communicate between microservices for maintaining eventual consistency.
Let us now view how microservices can communicate with each other.
It is a strong suggestion that microservices communicate with each other asynchronously. That does not totally exclude simple synchronous communication using a protocol like HTTP which follows the request-response paradigm.
If a client sends HTTP request to the API gateway and expects immediate response in a certain amount of time, then the API gateway which may be a microservice itself, sends a further request to the business microservice using the same HTTP request response paradigm. The business microservice responds synchronously to the API gateway which then forwards the reply back to the client.
This type of communication is used mainly for data queries and reporting.
Another synchronous communication is the push communication from the microservice to the clients, using messages. A technology like SignalR is very effectively used for this type of communication. For other type of operations, asynchronous communication is suggested to be used.
For communication between microservices, a more commonly used pattern is to use a message-based communication.
In this pattern, a message is sent with the command built-in into it through a broker software like a queue. After sending this message, the sending microservice does not wait for any response or reverse message, but continues with its own process. The message is picked up by the target microservice as and when it is free to do so. This is asynchronous communication.
There may sometimes be a response from the business microservice which is sent back in another message put in the queue. Most of the times, there may not be any response at all. The mechanism for the queue is sometimes a simple queue mechanism like RabbitMQ. Queue mechanism assures the delivery of the message even if the recipient is not available at the time the message is put in the queue. Such communication is usually between two microservices where there is only one recipient microservice.
In some cases, we intend to have multiple business microservices taking action on a single message which is sent by the sending microservice. This is made possible using asynchronous event driven communication.
Azure Service Bus which uses the publisher subscriber paradigm is one such mechanism to address multiple recipients of the same event. For example, if the price of a product has changed after the product was put in the Shopping Cart, the event of price change will be published by the catalogue microservice and then subsequently consumed by the Shopping Cart as well as the Order Processing microservice. This type of communication between the microservices is the most desired type of communication.
So far, we have discussed the logical structure of the microservices and how the database is part of that logical structure. Let us now see how that logical structure maps to the physical structure.
The most popular target to deploy microservices are Containers. Although it is possible to keep all sub services of the microservice in one container only, it is also possible to design a container for each such sub-services which is being provided by the microservice. There can even be a container which stores the database for the microservice.
Storing the microservices in a single container or multiple containers is entirely a decision based on the complexity of the microservice and the desired level of granularity that we require within the services of microservice itself from a scalability point of view.
Container – A Brief overview
Let’s now take a brief overview of containers. Containers provide a virtualized platform for the implementation of microservices. Containers are small virtualized processes which utilize the operating system of the host on which they are created. Unlike a virtual machine it does not have its own operating system.
Since it does not have a full operating system of its own, it usually is very small as compared to the virtual machine. This gives it an advantage over virtual machines, of creation and execution with very small resources.
It is a much more efficient way of virtualization of an application compared to the virtual machine.
A container usually runs only a single process. By forcefully creating other processes within the container, we may have multiple processes running in the container but that is a pattern which we should try to avoid as it goes against the intention of independence of microservices.
Docker and Kubernetes
Docker is one of the most popular open source Technologies that provides a platform for the containers to be created and run. Docker provides support for
– Building the image for the container
– Deploy the image so that a container is created
– Manage the instance of the image (which is the container)
– Tearing down the container which was created based on the image, after its need is over.
Another advantage that containers provide is the automation of management and orchestration of the instances of those containers. External services can create the instances off containers as needed and can destroy those when there is no need. These services can also scale the number of containers up or down based upon the need. Kubernetes is one of the most popular external services that manages docker containers and their hosts.
Docker has an implementation on Windows also, although basically Docker was created for Linux.
The implementation on Windows is called “docker for desktop”. The prerequisites to install docker for desktop is an operating system which is either Windows 10 for Windows Server 2016 or Windows Server 2019. On these operating systems, the service of the virtual machine platform should be enabled. We may also enable Windows subsystem for Linux which works as the backend for docker desktop. If we have not enabled Windows subsystem for Linux, then as a process of installation of docker desktop, Windows subsystem for Linux version 2 is installed. Docker for desktop also includes the installation of Kubernetes.
In this article, we have taken an overview of the following:
- The evolution of architecture of microservices as a response to the needs of the industry.
- Process of how to design the logical microservices and then the design of physical microservices.
- How Microservices are accessed by clients and patterns of communication between Microservices.
- Implementation details of Microservices – Containers.
- Introduction to Docker and Kubernetes.
This article was technically reviewed by Gouri Sohoni.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!