Microservices Best Practices Every Developer Should Follow

Arshad Suraj
Nerd For Tech
Published in
7 min readAug 23, 2021
Image from-sv.uio.no

This article will guide you through 9 best practices that a developer should follow when working on a microservice project. let’s take a deep look at each.

1. Have A Domain-Driven Design

Each service should have its own well-defined scope. Remove anything from your service that isn’t relevant to its scope and keep only the elements that are necessary to achieve the goal of the particular service.

While designing a microservice, make sure you have a fresh domain-driven design. If you are already running a microservice, just Rethink whether the design is domain-driven? you may not need to rewrite everything, but you can make little tweaks and make it a better-isolated service with an independent well-defined scope.

Using modules from several domains to create a single service is a really bad practice.

2. Do Not Hard-Code values

Assume that service “A” makes a call to service “B.” In this scenario, it’s bad practice to hard-code the address of service “B” in service “A” in order to find service “B”. Since it may work fine initially, but if the network team decides to alter the hostname or IP address of service “B,” service “A” will be unable to locate service “B” because it is hardcoded.

Therefore, The addresses of foreign services should not be hardcoded. For such, you should utilize service discovery tools.

3. Maintain Logging

It’s terrible to keep no-logs, but it’s also bad to have too many logs.

If you don’t keep track of your logs, you’ll never know what went wrong. In other hand, Assume, service “A” calls service “B,” and that service “B” calls service “C.” If service “C” fails, the error is logged in service “C” and the request is returned to service “B.” Since service “C” returns the error, service “B” reports the same error and returns to service “A”. Then since service “B” returns the error service “A” also logs the same error.

Therefore, in the above scenario, the same error is logged in three different places. So this is obviously a bad practice, right?

So the solution is “Fail Fast, Log Later!”. If something fails, return immediately and don’t log everywhere. Only Log the error, where you initiated the process. So for the above scenario, you should log the error only in service “A”.

Furthermore, you must log the error with some additional information to figure out where it went wrong. You can use a Correlation ID for this.

A Correlation ID is a unique, randomly generated identifier value that is added to every request and response. The initial Correlation ID is passed to other services when you call other services in a microservice architecture. If that service makes call to another service, the Correlation ID will be passed to that service as well. This helps us to find out where the error actually happened in a microservice architecture.

But make sure you don’t miss any information while logging. Some fields you can add into the log statement to make it more understandable are listed below,

  • Date and time
  • The IP address of the server
  • The IP address of the client request
  • Name of the service
  • Log level
  • Stack errors

4. Versioning

As time passes, we will make several modifications to our services as needed. As a result, numerous versions of the same service may be offered. As a result, one of the service versions may break the system, while another may contain bug patches, and so on. As a result, a correct versioning approach is required to determine whether this version of the service is compatible for this system. So, in this section, we’ll look at a common versioning technique called Semantic Versioning.

Semantic Versioning is a three-part number of the format X.Y.Z where:

  • A major version is denoted by the letter X.
  • A minor version is denoted by the letter Y.
  • A patch is represented by the letter Z.

So, Semantic Versioning is in the form of Major.Minor.Patch

image from — geekforgeeks.org

Points to remember when it comes to semantic versioning:

  • Because no bug fixes have been made in the beginning, the initial version starts at 0.1.0, not 0.0.1. we start with a set of features as a first draft of the project.
  • 1.0.0 will be the first stable version. Before 1.0.0, there is only the Development Phase, during which you concentrate on getting things done.

Note:- “Force upgrade” is a concept we should focus on when migrating to another version.

Let’s understand this by an example,

Let’s assume you have a service called A. This service A calls service B, and service B has to be updated. You are modifying the structure of service B’s database during this update. As a result, the service requests would be different. Your service A will break if you deploy the updated version of service B.

So how can we resolve this Issue?

Versioning can be used here, you can increment the major version number and deploy this as a separate service (now we have 2 versions of service B). However, we should continue to allow traffic to the older version and warn our customers that they must upgrade to the newer version before a specific date, as the older version of service B would be shut down after that date. when that day comes or After all of your clients have migrated to the new version, you can shut down the older version.

There is another way to solve this issue. At first, we should have more older version instances and less newer version instances. When customers upgrade to a newer version, you can raise the number of newer version instances while decreasing the number of older version instances. You can handle your services in this manner without causing any disruption to your customers. this method is known as Elastic mechanism.

5. Authentication and Authorization

A request in a microservice architecture may pass through many services. As a result, if each service tries to validate the user, your round trip will be longer.

For example:- Assume you are using an OAuth token to validate consumers, if you have three services and each one takes 20 milliseconds to validate a user, the system will take 60 milliseconds, just only to validate the user.

The best practice is to create a separate identity validation service and direct all requests that come into your service layer to it. If the validation is successful, you can direct them to the rest of the path. This is referred to as “Force Forwarding”. by this technique you can prevent some latency.

6. Dependency

Every service in the microservices world should be able to run independently without relying on others. They must be treated as isolated units.

Consider the following scenario: we have two services, A and B. If A is reliant on B, then when we want to deploy A, failing to deploy B in production will cause plenty of issues.

As a result, independence is necessary. Each of these services should be able to be deployed independently of one another, with no shared dependencies. As a result, we should be able to launch Service A or B independently without fear of breaking of others.

7. Make Executable Contracts

This is a process of a consumer and company signing a contract. This contract is executable in the sense that it cannot be broken at any cost. A set of well-defined test cases/test scripts/requests could be included in this contract. Every time an application is built, these tests will be executed. If all of the test cases passed, you haven’t broken any consumers. However, if any of those tests fail, you can fix them.

8. Fault Tolerance

Fault tolerance is the ability of a system to keep operating properly even if one or more of its components fail. We have various failure scenarios since Microservices architecture contains multiple services. We must effectively manage any failures that occur.

We require a robust fault tolerance method. If a service times out, fails slowly or takes a long time to react, you must ensure that it fails fast in order to avoid creating a queue that is waiting for the service’s response.

There are several patterns to achieve Fault-tolerance such as circuit breakers, Timeouts, Retries etc.

9. Documentation

You should document everything you do. So that another person (Developer, user of your service etc.) can understand what you’ve done.
You can use Swagger to write documents in a technical way. Swagger will convert any technical terms you put into the Swagger console into attractive documentation. It also offers users with a user interface (UI) so that they can refer to it and try out various services.

Keep Learning ❤️

--

--