📜 ⬆️ ⬇️

Microservice design patterns

Hello, Habr!

In the near future, read the post about the Russian translation of Sam Newman's long-awaited book " Creating Microservices ", which has already gone to the stores. In the meantime, we offer to read the translation of the article by Arun Gupta, the author of which describes the most interesting design patterns applicable in microservice architecture


The main characteristics of microservice applications are described in the article " Microservices, Monoliths and NoOps ". These characteristics are functional decomposition or subject-oriented design, well-defined interfaces, a clearly published interface, the principle of a single duty, and potential multilingualism. Each service is fully autonomous and full-stack. Accordingly, the change in the implementation of one service does not affect the others, and the exchange of information occurs through well-defined interfaces. This application has a number of advantages, but they are not given for nothing , but require serious work related to NoOps.
')
But suppose that you imagine the scale of this work, at least partially, that you really want to create such an application and see what happens. What to do? What will be the architecture of such an application?
Are there design patterns that optimize the interaction of microservices?



To create a high-quality microservice architecture, it is necessary to clearly separate the functions in your application and team. In this way, weak linking (REST interfaces) and strong coupling can be achieved (many services can be linked together, defining higher-level services or an application).

Creating “verbs” (for example, Checkout) or “nouns” (Product) as part of an application is one of the effective ways to decompose existing code. For example, product, catalog and checkout can be implemented as three separate microservices, and then interact with each other, providing full functionality of the order basket.

Functional decomposition provides flexibility, scalability and other features, but our task is to create an application. So, if the individual microservices are identical, how to put them together to implement the functionality of the application?

This will be discussed in the article

Pattern Aggregator

The first and perhaps the most common design pattern when working with microservices is the “aggregator”.

In its simplest form, the aggregator is a regular web page that invokes many services for implementing the functionality required in the application. Since all services (Service A, Service B and Service C) are provided using the lightweight REST mechanism, the web page can extract the data and process / display it as needed. If any processing is required, for example, to apply business logic to the data received from individual services, then for this you may have a CDI component that converts the data so that they can be displayed on a web page.



The aggregator can also be used in cases when nothing needs to be displayed, but only a higher-level composite microservice, which other services can consume, is needed. In this case, the aggregator will simply collect data from all individual microservices, apply business logic to them, and then publish microservice as the REST endpoint. In this case, if necessary, it can be consumed by other services that need it.

This pattern follows the DRY principle. If there are many services that need to access services A, B and C, then it is recommended to abstract this logic into a composite microservice and aggregate it as a separate service. The advantage of abstraction at this level is that individual services, say, A, B, and C, can evolve independently, and the business logic will still execute a composite microservice.

Please note: each individual microservice (optional) has its own caching levels and databases. If the aggregator is a composite microservice, then it may also have such levels.

The aggregator can also be independently scaled both horizontally and vertically. That is, if we are talking about a web page, then additional web servers can be bolted to it, and if it is a composite microservice using Java EE, then additional WildFly instances are bolted to it to meet growing needs.

Pattern Proxy

The pattern of "intermediary" when working with microservices is a variant of the aggregator. In this case, the aggregation should occur on the client, but depending on the business requirements, an additional microservice can be invoked.



Like the aggregator, the mediator can independently scale horizontally and vertically. This may be necessary in a situation where each individual service should not be provided to the consumer, but should be launched via the interface.

The mediator may be formal (dumb), in which case he simply delegates the request to one of the services. It may also be smart, in which case the data is subjected to some kind of transformation before being sent to the client. For example, the presentation layer for various devices may be encapsulated in an intelligent mediator.

Chained Design Pattern

Micro service design pattern “Chain” gives a single consolidated response to the request. In this case, service A receives a request from a client, communicates with service B, which, in turn, can contact service C. All these services are likely to exchange synchronous “request / response” messages over HTTP.



The most important thing to remember here is that the client is blocked until the entire communication chain of requests and responses, i.e. Service <-> Service B and Service B <-> Service C. A request from Service B to Service C may look completely different than from Service A to Service B. Similarly, the response from Service B to Service A may differ fundamentally from that of Service C to Service B. This is most important in all cases when the business value of several services is summed up.

It is also important to understand here that you cannot make the chain too long. This is critical, because the chain is synchronous in nature, and the longer it is, the longer the client will have to wait, especially if the response is to display the web page on the screen. There are ways to circumvent such a blocking request and response mechanism, and they are covered in the following pattern.

A chain consisting of a single microservice is called a “single chain”. Subsequently, it can be expanded.

Design Pattern "Vetka" (Branch)

Micro-service design pattern “Vetka” expands the “Aggregator” pattern and provides simultaneous processing of responses from two chains of microservices, which can be mutually exclusive. This pattern can also be used to call different chains, or the same chain - depending on your needs.



Service A, be it a web page or a composite microservice, can competitively cause two different chains - in which case it will resemble an aggregator. In another case, service A can call only one chain, depending on what request it receives from the client.

Such a mechanism can be configured by implementing JAX-RS endpoint routing, in which case the configuration should be dynamic.

Shared Data pattern

One of the principles of microservice design is autonomy. This means that the service is full-stack and controls all components — user interface, middleware, persistence, transactions. In this case, the service can be multilingual and solve each task using the most appropriate tools. For example, if, if necessary, you can apply the NoSQL data warehouse, then it is better to do so, and not to fill this information into the SQL database.

However, a typical problem, especially when refactoring an existing monolithic application, is associated with database normalization - so that each microservice has a strictly defined amount of information, no more, no less. Even if only a SQL database is used in a monolithic application, its denormalization leads to duplication of data, and possibly to inconsistency. In the transition phase, in some applications it is very useful to apply the “Shared data” pattern.

In this pattern, several microservices can work on the chain and share cache stores and databases. This is only useful if there is a strong link between the two services. Some may see this as an anti-pattern, but in some business situations such a pattern is indeed appropriate. It would definitely be antipattern in an application that was originally created as a microservice.



In addition, it can be considered as an intermediate stage that needs to be overcome until microservices become completely autonomous.

Asynchronous Messaging Pattern

With all the prevalence and clarity of the REST pattern, it has an important limitation, namely: it is synchronous and, therefore, blocking. It is possible to provide asynchrony, but it is done differently in each application. Therefore, in some microservice architectures, message queues may be used, rather than the REST request / response model.



In this pattern, service A can synchronously call service C, which will then asynchronously communicate with services B and B using a shared message queue. Communication Service A -> Service C can be asynchronous, say, using web sockets; this achieves the desired scalability.
The combination of the REST request / response and messaging publisher / subscriber model can also be used to achieve your goals.

I also recommend reading the article Coupling vs Autonomy in Microservices , which describes which communication patterns are convenient for using with microservices.

Source: https://habr.com/ru/post/275633/


All Articles