Best Practices for Microservices 📳(MICROSERVICES — Part-2)
Microservices: Best Practices ✨
In this article, I’m going to discuss nine best practices that you should follow when dealing with microservices.
① Always consider using Domain-Driven Design.
✹ It should have a well-defined scope. Consider the below example,
Example:
Assume that there’s a “Rent a car” application. There’s a customer and it can represent two different services but from two different aspects. Consider those as Customer Registration and Support service. Hence the same customer is involved in both modules but in two different aspects. If we consider the Support service, the main purpose/domain of this module is customer support.
So accordingly we should have a clear understanding of the borders of each service and should be able to ensure that each of the service’s functions is not needed by other services and only needed by that particular service.
② Do not use Hard-coded values
✹ Hard Coding a value means it has been declared as unchanging and cannot be changed directly. When a value is hardcoded, it remains the same as it was throughout the execution of the computer program.
Let’s understand this by the below example:
Assume there are two services as 🅰 and 🅱. Now the service 🅰 is calling the service 🅱. To call 🅱, you should have its address. So most of the developers hardcoded this address (It may be a hostname, IP address, URL…etc.) on the service 🅰.
But this is Not a Good practice 😑,
If your networking team is decided to change the hostname, URL …etc. then you'll have to face issues. Because you need to send other deployments. So this is not a good way. In order to stay away from this, we can use some kind of service discovery tool to discover the 2nd service.
✹ So in a conclusion, you should not hardcode the hostnames, URLs…etc as they can change dynamically and therefore should use a service discovery tool for this purpose.
③Logging
Having too many logs or having no logs are BAD🤢
WHY🙄
Because when you try to access through logs/debug through logs, the same error logs twice. So it will really hard. But if there were no logs, the application will become lost control. Also, It can be a really hard point if we Log for each service and if the number of services starts increasing. You will have to go through each service’s logs to identify the correct log if you need to debug or identify an issue, so it will be really tough job.
So some of the good practices for this are:
Using a Correlation ID
You can assign a correlation ID(unique ID) to an incoming request, and it will help to recognize the request uniquely in each service.
Example:
Think there are three Services as 🆇, 🆈, and 🆉 . And we have tagged each call with a correlation ID. Assume you find an issue in service 🆈, so you should know whether that issue/error was created by the request coming from Service 🆇 or the request sent to Service 🆉. So the most convenient path to correct that error is to find and know all possible requests in the services (🆇 and 🆉) that are related to Service 🆈. So as we are having correlation ID now, then what we have to do is just to look at the logging system for that particular unique ID. And we will be able to get all logging details from services that were a section of the main request to the application.
📍 It's better to add context as it is very much useful if we are having enough information about an error or issue. Below are some specific fields that can add,
- Date and time
- The IP address of the server
- The IP address of the client's request
- Name of the service
- Stack errors
④ Versioning
Below are two popular standard techniques in versioning.
Semantic versioning
This represents each of the releases by using three non-negative integers in the “MAJOR.MINOR.PATCH” format(as shown in the below figure)and each of these integers indicates exact meaning.
Calender versioning
Calendar Versioning is also having the same format similar to Semantic versioning. Calendar Versioning uses the calendar-based format that will reflect the date of release into the version number. (as shown below)
Example:
Assume there’s a service named 🅰 and 🅱 calls the service 🅱. And now you have to update the service 🅱. Accordingly when you are updating the 🅱 service request would be different. And if you deploy the updated new version of the service 🅱, the service 🅰will break.
To avoid that, you can increment the major version numbers and can deploy this as a separate service. But if the traffic is still taking for the previous version, you can talk to the consumers and ask them to update to the new version 🅱. After the migration of all the consumers is completed, you can shut down the previous version. For this, we can use the Elastic mechanism.
⑤ Authorization and Authentication mechanism in Microservices
✹ If every system starts to validate the user, then it will lead to increase latencies. So to get avoid that, it's better to have a separate identity validation service to validation services and you can direct all the requests which come to the service layer to their identity validation service. If the validation is successful, you can direct them to the rest of the path.
Example: In a “Rent a car” system, when a user tries to allocate a vehicle to the customer, assume it invokes three different services. Consider that you are using authentication tokens to validate customers. If it takes 20 ms to validate the authentication token it will probably take 60 ms to validate each service separately. So to get avoid that, it’s better to have a separate identity validation service to validation service.
✹ So this is a good practice as you can change the validation or authentication or authorization process based on your algorithm/service decision at any given time.
⑥ Dependency
✹ Should avoid any dependencies and should be deployed independently.
Example: Assume there are 3 different services 🅰, 🅱, and 🅾. If you need to deploy 🅰 and if you fail to deploy the other 2 services together, then it will lead to having a set of problems.
So Independency is a must!🤗
⑦ Make Executable Contracts
✹ Lets’s understand this by the example below,
Example: Assume there are 3 different services 🅰, 🅱, and 🅾. In this structure, if you deploy the service 🅱 , certain consumers will break. So to avoid that you can have a contract with the consumer. And this contract is an executable one.
This contract may include test cases, test scripts, ….etc. So whenever you make a build, those tests will be executed automatically. (If all the test cases passed, that means you did not break any consumers. But If any of those tests are failed, you can fix them. )
⑧ Fault Tolerance
✹ What is Fault Tolerance?
✹ Since microservice architecture has multiple services, there’s a possibility of getting failures. For this, we need Fault Tolerance.
Example: Assume there are 3 different services 🅰, 🅱, and 🅾. Then 🅰 calls 🅱 and 🅱calls 🅾. If the service 🅱 is running out of time, that means, the service 🅱 is slowly failing. That means you'll FAIL FAST. So, for this, we need Fault Tolerance.
✹ We can achieve Fault-tolerance by using circuit breakers, Timeouts, Retries..etc.
⑨ Documentation
✹ The documentation is much more essential as there should have documentation to make it clear and understandable to others.
✹ For this, we can use Swagger to write documents in a technical way.
Swagger can be defined as a collection of open-source tools that supports us to design, build, documenting & consume REST APIs.