1. Introduction
1.1 The beginnings of programming
To begin, let’s go back to the period when there were no microservices. At the beginning of computer appearances, the applications contained all the necessary instructions for executing the programs. And everything was compiled and installed on the machine, like the first text editors or the first video games. Then, applications became more and more feature rich and, therefore, more complex. Thus, maintenance of the entire application in a single block of code was painful, which pushed developers to adopt “best practices”? In particular writing small modules responsible for making some features, and building the application from these modules. An independent module can be used in another application and so on. Managing complexity was no longer a problem. But, we still had to compile and install on one machine.
1.2 Arrival of Web applications and monolithic architecture
We can say that web applications have revolutionized the way we build programs. The storage and computing power of a single computer being limited, people leaned more and more towards web applications, where only the Internet browser must be installed on the computer. So there was no storage problem. Just sending and receiving the html code done on the computer, so just a low CPU performance is required from the user. The developers had to write the applications into several independent modules. Then they compiled and deployed everything on a server that the user can join with his browser. This is called monolithic architecture. Everything seems fine so far, but can we do better?
The difficulties that can be encountered with monolithic architecture:
- Complication of deployment
- Because all modules are deployed at one time to the server, any change in any application module requires the redeployment of the entire application and therefore threatens its full functioning.
- Non-optimized scalability
- The only way to increase the performance of an application designed in monolithic architecture, following an increase in traffic, for example, is to redeploy several times on multiple servers. However, in the majority of cases, it is necessary to increase the performance of a single feature of the application. But redeploying to multiple servers will increase the performance of all features, which may be unnecessary and wasting computing resources.
These two main difficulties gave rise to the micro-services architecture that will be detailed in this article.
2. What is a microservice?
A micro-service is a small application designed to make a single feature. For example, an application that sends text to an email address can be a micro-service. It is a single brick of the global application, an independent brick with a single independent responsibility. A person, or even a team can work freely and autonomously on a micro-service, design it at their choice, code it with a programming language that they select themselves and deploy it on the server they want. Then they provide the micro-service as an API for example.
The overall application will eventually be the combination and integration of all micro-services.
3. The benefits of architecture
3.1 Separation of responsabilities
Each team can work on a micro-service separately without worrying about the overall architecture. This allows newcomers to integrate easily because they will only have to read and understand the micro-service they will be working on instead of documenting the entire application. This can be painful if it is a large application. The addition of new features becomes very fast as well as the location of the bugs.
3.2 Scalability
The microservices architecture makes it possible to increase the performances of applications in an efficient way. For example, if you need a feature to perform better, simply replicate the corresponding micro-service and deploy it on multiple servers all alone. Thus, we will avoid replicating the entire application each time one of these components is lacking resources.
4. Difficulties
While micro-service architectures help us optimize the way we develop, deploy, and use physical resources, they have some challenges to consider when designing.
4.1 More compulsory tests
Since any micro-service must be independent and separate from the rest of the application, the tests must be independent. This means that any call to another microservice must be made fun with code that behaves as such. Mocking must be maintained at the same behavior of the third-party micro-service, and any difference with it can let bugs go unnoticed in production.
4.2 Latency of the network
The calls between micro-services are made through the network; HTTP calls for example, which is much slower than function calls in modules. Sometimes we can even deal with calls that do not succeed or that fail, it is impossible in monolithic.
4.3 Data Sharing
In micro-service architectures, you have to be very careful about the synchronization of data between the different replicas of a micro-service, because there will always be concurrent access to the data.
For example, two instances of the service modify one data at the same time, or a configuration change processed by one instance must be communicated to all other replicas.
4.4 Monitoring
Monitoring is easier in monolithic than in micro-services. It is necessary to set up a monitoring agent on each server hosting a micro-service and monitor the traffic of each of them; in the process of monitoring a single multi-monolithic process.
5. Good Practices
The difficulties mentioned above lead us to adopt good practices to facilitate and optimize micro-services development:
5.1 Starting in monolithic
It is highly recommended to start with a monolithic design first and to separate the micro-services little by little, according to our vision of the future functionalities and the traffic of the production. This makes it possible to reduce the number of micro-services and to choose the services to be separated.
5.2 Check independence of microservices
To minimize the dependence between micro-services and consequently the sharing of data between them, each micro-service must have only one well-defined responsibility and contain only the functionalities linked to it.
5-3 Stabilizing the interface contract
The API of each micro-service must change as little as possible in order to maintain independence and not disrupt the work on micro-services dependent on it. So if you are forced to upgrade the API, it is highly recommended to keep compatibility with the old version or then launch and synchronize the two versions in parallel for a sufficient period. This will allow other teams to migrate to the new version.
6. Example
In this part, we set up a micro-service architecture of a basic application to illustrate the utility of this architecture and its benefits. The good practices mentioned above will also be followed.
6.1 Use Case
Let’s imagine that we want to develop an application that plays the role of an online sales market. For simplicity, products are available from vendors, and their manipulation is done through API calls on their servers. Our app (let’s call it speedBuy) must expose products to customers by API call to see the features of a product and to be able to buy it. We will therefore need to provide customers with the following three end-points:
- GET speedBuy / catalog: list the catalog of all available products
- GET speedBuy / details / <id>: list the product characteristics number <id>
- POST speedBuy / buy / <id>: buy the product number <id>
The majority of vendors accept the same end-points to display and sell their own products with json as the data format. So we decide to provide our API in json too. However the rest of the providers only accept the xml format, the end-points are as follows: “vendorX / xmCatalog”, “vendorX / xmlDetails / <id>”, “vendorX / xmlBuy / <id>”.
Sample answers:
Query: GET speedBuy / catalog
Reply:
{ "1": { "nom": "Produit1", "description": "caractéristique du produit 1", }, "2": { "nom": "Produit2", "description": "caractéristique du produit 2", }, "3": { "nom": "Produit3", "description": "caractéristique du produit 3", }, ... }
Query: GET speedBuy/details/3
Reply:
{ "nom": "Produit3", "fournisseur": "fournisseur10" "description": "caracteristique du produit 1", "prix": 50, "devise": "EUR", "note" : 4.5, "nombre_avis" : 500 }
Query: GET speedBuy/buy/3
“body” : { "nomAcheteur": "Bob Donut", "Payment": "CB 9999 9999 9999 9999", "adresse" : "123 rue Pierre Paul … " }
Reply:
{ "Status": "CONFIRME", "DelaiLivraison": 2 }
For suppliers that only accept xml format, the last answer for example will be as follows:
<?xml version="1.0" encoding="UTF-8"?> <root> <nom>Produit1</nom> <description>caracteristique du produit 1</description> <fournisseur>fournisseur3</fournisseur> <prix>50</prix> <devise>EUR</devise> <note>4.5</note> <nombre_avis>500</nombre_avis> </root>
6.2 Architecture
6.2.1 Monolithic
If we start a monolithic architecture, our application will have the following form:
6.2.2 The first microservice
After several iterations and following good development practices (refactoring and tests …) we get to have a production version that works.
Note that the module making the transformation from json to xml, because of its simplicity, was stable from the beginning and we had to redeploy it every time and launch its tests with all the deployment of our application. So we decide to separate it from our main application to just manage the logic of the business without worrying about this module.
Let’s start by separating our first micro-service, call the xmlRouter. He will be in charge of managing everything that is related to the transformation to xml and must allow speedBuy to behave as if all the suppliers accepted json as format and that it is one of them. It will thus provide the same three APIs of a json provider and will be responsible for aggregating in its response all the responses of the xml providers:
6.2.3 More optimization
The use of physical resources can be furthermore optimized, by making the number of replications of each service elastic. This means that the servers multiply at the peak and break free when the traffic decreases.
Conclusion
To conclude, the microservices architecture consist in breaking down the global application into several components responsible for a single functionality and communicating in a network with each other and with third-party applications. It allows easy deployment and optimized use of computing resources. It is a model of architecture adopted more and more by the companies and continues to evolve everyday; among the evolutions we count the model Function as a Service or Serverless.