
Hi, Habrozhiteli! We recently commissioned a book by
Chris Richardson to the print shop, the goal of which is to teach us how to successfully develop applications using microservice architecture. The book discusses not only the advantages, but also the disadvantages of microservices. You will learn in what situations it makes sense to apply them, and when it is better to think about a monolithic approach.
The focus of the book is on architecture and design. It is designed for anyone whose responsibilities include writing and delivering software, including developers, architects, technical directors and heads of development departments.
Below is an excerpt from the book "Using asynchronous messaging"
Use asynchronous messaging to improve accessibility
As you have seen, the various IPC mechanisms are pushing you to various compromises. One of these is related to how the IPC mechanism affects accessibility. In this section, you will learn that synchronous interaction with other services as part of request processing reduces the availability of an application. In this regard, when designing your services, you should use asynchronous messaging whenever possible.
')
First, let's see what problems synchronous interaction creates and how it affects accessibility.
3.4.1. Synchronized communication reduces availability
REST is an extremely popular IPC mechanism. You may be tempted to use it for interservice interactions. But the problem with REST is that it is a synchronous protocol: the HTTP client has to wait until the service returns a response. Every time when services communicate with each other over a synchronous protocol, it reduces the availability of the application.
To understand why this is happening, consider the scenario presented in Fig. 3.15. The Order service has a REST API for creating orders. To check the order, he refers to the Consumer and Restaurant services, which also have a REST API.
Creating an order consists of such a sequence of steps.
- The client makes an HTTP POST / orders request to the Order service.
- The Order service retrieves customer information by performing an HTTP GET / consumers / id request to the Consumer service.
- The Order service retrieves restaurant information by executing an HTTP GET / restaurant / id request to the Restaurant service.
- Order Taking verifies the request, using information about the customer and the restaurant.
- Order Taking creates an order.
- Order Taking sends an HTTP response to the client.
Since these services use HTTP, all of them must be available for the FTGO application to process the CreateOrder request. It will not be able to create an order if at least one of the services is unavailable. From a mathematical point of view, the availability of a system operation is a product of the availability of the services that are involved in it. If the Order service and the two services it calls have 99.5% availability, then their overall availability will be 99.5% 3 = 98.5%, which is much lower. Each subsequent service involved in the request makes the operation less accessible.
This issue is not unique to REST-based communication. Availability is reduced whenever a service must receive responses from other services to respond to a client. It does not help even the transition to the style of interaction "request / response" on top of asynchronous messages. For example, if the Order service sends a message to the Consumer service through a broker and starts to wait for a response, its availability will deteriorate.
If you want to maximize availability, minimize the amount of synchronous interaction. Let's see how to do it.
3.4.2. Disposal of synchronous interaction
There are several ways to reduce the amount of synchronous interaction with other services when processing synchronous requests. First, to completely avoid this problem, all services can be provided exclusively with asynchronous APIs. But this is not always possible. For example, public APIs generally adhere to the REST standard. Therefore, some services are required to have synchronous APIs.
Fortunately, in order to process synchronous requests, it is not at all necessary to execute them yourself. Let's talk about such options.
Using asynchronous interaction stylesIdeally, all interaction should occur in the asynchronous style described earlier in this chapter. Imagine, for example, that an FTGO application client uses an asynchronous request / asynchronous response style to create orders. To create an order, it sends a message requesting the Order service. Then this service asynchronously exchanges messages with other services and eventually returns the answer to the client (Fig. 3.16).
The client and service communicate asynchronously, sending messages through the channels. None of the participants in this interaction is blocked in anticipation of a response.
Such an architecture would be extremely robust, because the broker buffers messages until their consumption becomes possible. But the problem is that services often have an external API that uses a synchronous protocol like REST and, as a result, is obliged to immediately respond to requests.
If the service has a synchronous API, accessibility can be improved by replicating data. Let's see how it works.
Data replicationOne way to minimize synchronous interaction during query processing is data replication. The service stores a copy (replica) of data that it needs to process requests. To keep the cue up to date, it subscribes to events posted by the services to which it belongs. For example, the Order service may store a copy of data belonging to the Consumer and Restaurant services. This will allow it to process requests to create orders without accessing these services. This architecture is shown in Fig. 3.17.
Services Consumer and Restaurant publish events whenever their data changes. The Order service subscribes to these events and updates its replica.
In some cases, data replication is a good solution. For example, chapter 5 describes how the Order service replicates data from the Restaurant service in order to be able to check menu items. One of the drawbacks of this approach is that sometimes it requires copying large amounts of data, which is inefficient. For example, if we have many customers, storing a replica of data belonging to the Consumer service may not be practical. Another disadvantage of replication lies in the fact that it does not solve the problem of updating data belonging to other services.
To solve this problem, the service may delay the interaction with other services until it responds to its client. This will be discussed further.
Completion of processing after returning the answerAnother way to eliminate synchronous interaction during query processing is to perform this processing in the following steps.
- The service only checks the request with data available locally.
- It updates its database, including adding messages to the OUTBOX table.
- Returns the answer to its client.
During the processing of a request, the service does not communicate synchronously with any other services. Instead, he sends them asynchronous messages. This approach provides a weak connectivity of services. As you will see in the next chapter, this process is often implemented in the form of narration.
Imagine that the Order service is acting like this. It creates an order with a PENDING state and then checks it, exchanging asynchronous messages with other services. In fig. Figure 3.18 shows what happens when the createOrder () operation is called. The chain of events looks like this.
- The Order service creates an order with a PENDING state.
- The Order service returns to the client a response with the order ID.
- The Order Service sends a message ValidateConsumerInfo to the Consumer service.
- Service Order Sends a ValidateOrderDetails message to the Restaurant service.
- The Consumer service receives a ValidateConsumerInfo message, checks if the customer can place an order, and sends a ConsumerValidated message to the Order service.
- The Restaurant service receives the message ValidateOrderDetails, checks the correctness of the menu items and the ability of the restaurant to deliver the order to the specified address, and sends the OrderDetailsValidated message to the Order service.
- The Order service receives messages ConsumerValidated and OrderDetailsValidated and changes the order status to VALIDATED.
And so on…
The Order Service may receive the ConsumerValidated and OrderDetailsValidated messages in any order. To know which one he got first, he changes the order status. If the first message came ConsumerValidated, the order status changes to CONSUMER_VALIDATED, and if OrderDetailsValidated, it changes to ORDER_DETAILS_VALIDATED. Upon receiving the second message, the Order service assigns the status VALIDATED to the order.
After checking the order, the Order service performs the remaining steps to create it, which we will discuss in the next chapter. A remarkable side of this approach is that the Order service will be able to create an order and respond to the customer, even if the Consumer service is unavailable. Sooner or later, the Consumer service will recover and process all pending messages, which will allow you to complete the verification of orders.

The disadvantage of returning a response before the request is fully processed is that it makes the client more difficult. For example, when the Order service returns an answer, it gives minimal guarantees about the status of the order just created. He answers immediately, even before checking the order and authorizing the client’s bank card. Thus, in order to find out whether the order has been successfully created, the client must periodically request information or the Order service must send him a notification message. Despite the complexity of this approach, in many cases it is better to prefer it, especially because it takes into account problems with managing distributed transactions, which we discuss in Chapter 4. In Chapters 4 and 5 I will demonstrate this technique using the example of the Order service.
Summary
- Microservice architecture is distributed, therefore interprocess communication plays a key role in it.
- The development of the service API should be approached carefully and carefully. The easiest way to make backward compatible changes is because they do not affect the customer experience. When making breaking changes to the service API, it is usually necessary to maintain both the old and the new version until the clients update.
- There are many IPC technologies, each with its own advantages and disadvantages. The key decision at the design stage is the choice between synchronous remote procedure calls and asynchronous messages. The easiest to use are synchronous protocols like REST, based on a remote procedure call. But ideally, to increase availability, services should interact using asynchronous messaging.
- To prevent an avalanche-like accumulation of system failures, a client using a synchronous protocol must be able to cope with partial failures — that the called service is either unavailable or exhibits high latency. In particular, when executing requests, you should count down the waiting time, limit the number of overdue requests, and use the “Fuse” template to avoid appeals to a failed service.
- An architecture using synchronous protocols must contain a discovery mechanism so that clients can determine the network location of service instances. The easiest way to stop is on the detection mechanism that the deployment platform provides: on the “Server-side detection” and “Third-party registration” templates. An alternative approach is to implement application discovery at the application level: “Client-side Detection” and “Self-Registration” patterns. This method requires more effort, but is suitable for situations where services are running on multiple deployment platforms.
- The message and channel model encapsulates the details of the messaging system implementation and becomes a good choice when designing this type of architecture. Later, you can bind your architecture to a specific messaging infrastructure, which usually uses a broker.
- The key difficulty in the exchange of messages associated with their publication and update the database. A good solution is to use the “Event Publishing” template: the message at the very beginning is written to the database as part of a transaction. Then, a separate process retrieves the message from the database, using the "Interrogating Publisher" or "Transaction Log Tracking" template, and sends it to the broker.
»More information about the book can be found on
the publisher site»
Table of Contents»
ExcerptFor Habrozhiteley 30% discount on pre-order book on coupon -
Microservices