Scaling down microservices

Microservice architecture became very popular in the last years. This approach has a lot of benefits, which are widely praised at conferences. Unfortunately, it is very common to forget about the problems and difficulties we may encounter.

Those who know me a little, know that I have always been skeptical of this approach. In this context, it may seem surprising that in the current project I use this approach with success. In this case, it was a natural process of division of the system into separate microservices and adding new ones when required. As a result, the entire system consists of several to a dozen or so services. The number does not seem to be very high but may become problematic when the system has to be installed on a single, not very powerful machine. It is much easier to share limited resources between modules in a single monolithic application.

I would like to show you how microservice-based architecture can be easily scaled down to effectively support smaller deployments. Examples are using Spring eco-system, but the general idea should work in any environment.

The way of designing microservices architecture

There are two ways to develop microservice-based architecture. Design it upfront or split existing monolith application into separate microservices. Regardless of the approach, it is necessary to define clear interfaces between services.

In case of monolith application, it may turn out that despite clear API definition there are some ‘shortcuts’ which makes it hard to divide it into independent pieces. Error handling based on exceptions may also become a problem.

In case of an upfront design of microservice, it may be a tricky job to define a common, reusable set of data structures.

Building modular applications

Let’s assume that we want to design a system with several modules, which can be easily deployed as a monolith application or a set of distributed microservices. Below I will describe the following steps. As an example, please imagine a complex IoT system. One of its modules is device management service. It is responsible for keeping a registry of devices and managing their configuration.

1. Define a separate API module

It is very important to start with defining a clear interface of a module. It defines main functionalities, common data structures, exceptions. The module with the API definition is the first step. In Spring / Maven environment it should be a separate module or project without any infrastructural dependencies. Just a plain definition of functionality and data structures.

It is just a fragment of a complex domain. Some classes are skipped, the others are simplified for brevity.

2. Define client modules

Based on the API module we should define remote and local client modules.

Local client module

The local client module should use the actual implementation of the API module and should embed whole functionality. Adding dependency to the local client module should effectively mean that whole functionality is embedded in the client application. It should be handled like any other library. For convenience, it may reuse Spring configuration of the device module by importing it.

Remote client module

Remote client module may use eg. HTTP client to realize functionality defined in API. In Spring environment the client can be easily defined with Feign.

On the other side, there has to be a server-side implementation, which supports all functionalities defined in API.

3. Error handling

It is very important to implement proper error mapping mechanisms. When a remote client is used, error handling based on exceptions is not an easy option. Making it transparent to the API client requires additional mapping of exceptions on a server-side to some transport-layer specific solution. The reverse operation, mapping to exceptions, has to take place on the client-side. Spring environment allows defining ErrorHandlers on the server-side and ErrorDecoders on Feign client side. Additional effort is required to map technical errors related to technical issues, specific to the transport layer.

Mapping exceptions on the server-side

Decoding on the client-side

On the client-side, we have to define ErrorDecoder, which will be used by FeignClients to create Exceptions based on received responses.

It requires some small enhancement of ErrorResponse:

It is important to update the remote client configuration to enable usage of ErrorDecoder by Feign clients.

4. Using client modules

First, let’s take a look at the dependencies between modules we have already prepared and their responsibilities:

  • devices-api – contains all interfaces and data structures
  • devices-impl – contains the implementation of all device module features
  • local-devices-client – contains local devices module client which is simply using an interface implementation from “devices-impl” module
  • remote-devices-client – contains remote devices module client which is using HTTP protocol to communicate with “standalone-devices-app”
  • standalone-devices-app – it is a standalone application which provides HTTP API to access devices module features

Let’s assume we have a module A, which depends on features provided by devices module. We want to have an option to deploy module A as a monolith, with devices module embedded, or both modules as separate applications.

We can achieve that by defining separate applications that aggregate module A and a specific device module client. It’s just a matter of defining a high-level configuration which adds a specific dependency to @Configuration with remote or local implementation of interfaces defined in the API and using it in Spring Boot application.

Such a solution assumes that module A depends only on the device module API. It is a responsibility of an aggregate configuration to provide an implementation of the API, local or remote one.

Summary

Microservice architecture has a lot of benefits, but it is crucial to remember that it adds additional complexity from the operational point of view. I have shared some hints about how you can easily replace a remote call to external microservice with calling the method from an external library embedded into the application. I hope you enjoyed it.

Please follow and like us:

Related Post

Leave a Reply

Your email address will not be published. Required fields are marked *