Good microservice design can make later maintenance upgrades easier, which would otherwise be a headache.

The following design principles are strongly recommended:

  • Single responsibility
  • High cohesion
  • Low coupling
    • Hide the internal implementation
    • Avoid code base sharing
    • Avoid data overexposure
    • Avoiding database sharing
    • Minimize synchronous calls
    • Minimize Hardware Sharing
    • Avoid platform-specific techniques

These three principles are at the heart of object-oriented design and also apply to microservice design.

1. Single responsibility

Each microservice should have only one responsibility.

For example, a microservice has two main functions:

  • Commodity classification management
  • The shopping cart

Putting them together doesn’t seem to be a problem because the same technologies are used, the functions and data are closely linked, and the same development team is usually responsible for the organization.

However, this creates a lot of code coupling between the two functions.

After a long period of time, the same problems as single architectures arise: difficult to maintain, test, and deploy.

Therefore, according to the principle of “single responsibility”, it should be divided into two micro-services.

2. High cohesion

Closely related actions should be kept together.

For example, there are two microservices:

  • The order management
  • Order amount statistics

The Order Amount Statistics service needs to request the Order Management service to get the required data.

Such as price, tax, service charge…

At first everything was fine, but then one day taxes were added and new calculation rules had to be changed.

So, the order service needs to provide new data, and the dollar count service needs to change the way it calculates.

That is, each change basically requires two services to change together, and is tightly coupled.

Because the logic of the order amount statistics service is only related to orders, it should be incorporated into the order service.

Bring closely related behaviors together to achieve high cohesion.

3. Low coupling

Changes to one service do not affect other services.

There are many aspects to this principle.

3.1 Hide the internal implementation

For example, in the previous section of “High cohesion”, the amount statistics service was incorporated into the order management service. Then, does the “statistical interface” in the amount statistics service need to be exposed?

It is now an internal function of the order service, and the statistical results can be used as data in the order object, so they do not need to be exposed to prevent misoperation and unnecessary coupling.

3.2 Avoid sharing code libraries

Sharing code is really convenient, but it makes the underlying code too correlated.

This can be awkward for future upgrades, such as when a service wants to upgrade the language version, but the shared library is using a lower version, and some of its usages are out of date in the higher version.

It’s not realistic to try to avoid it perfectly, just try to avoid it.

For example, instead of sharing, each service rebuilds the wheel so that there are boundaries between services.

However, this approach only applies to libraries that need to be shared, so it is very stable and does not need to be changed, otherwise all related services would need to be changed.

Another example is to reduce the granularity of shared libraries to avoid forming large libraries with special functions.

Large database will inevitably lead to a very wide range of references, impact surface.

If the granularity is small, fewer services are involved.

3.3 Avoid data overexposure

For example, a user service has an interface to get user details and return all information about the user.

When a shopping cart service retrives user information, it gets full data, including payment information, for example.

This is not necessary, just return the user’s basic attributes.

Special attributes should be provided through a separate interface.

Overexposure can increase coupling between services.

3.4 Avoiding Database Sharing

When a service wants to get data from another service, it should only do so through the interface, not directly from the other service’s database.

Otherwise, this data-level coupling can be a nightmare.

3.5 Minimizing Synchronous Invocation

For example, when creating an order, many other services are invoked, such as users, goods, payments, inventory, and logistics.

Are interfaces that invoke services directly synchronized?

Unrealistic. If one of the service interface calls fails, the order creation fails.

It is best to use an event-driven asynchronous invocation.

Synchronous invocation causes network congestion and has high requirements on the availability of the invoked service. Therefore, exercise caution when using synchronous invocation.

3.6 Avoiding Hardware Infrastructure Sharing

Services are well designed, but it can be painful if hardware deployment is not well planned.

For example, if two services are deployed on the same server and service B is very resource-intensive, service A may become unusable.

Therefore, do not ignore the key point of hardware, and make balanced deployment based on the characteristics of each service.

3.7 Avoid using platform-specific technologies

Java RMI, for example, is fine for remote calls, but it is a platform feature that requires both sides of the service to use a set of technologies, and this high coupling is less liberating than platform-independent REST.