- 10 minutes to read
This content is an excerpt from the .NET Microservices Architecture for Containerized .NET Applications eBook, available at.NET Documentsor as a free downloadable PDF that can be read offline.
In a monolithic application running in a single process, components invoke each other through language-level method or function calls. They can be tightly coupled if you are creating objects with code (e.g.
new class name()) or it can be called decoupled if you use Dependency Injection when referencing abstractions instead of concrete object instances. Either way, the objects run within the same process. The biggest challenge when moving from a monolithic application to a microservices-based application is changing the communication mechanism. A direct conversion of in-process method calls to RPC calls to services will cause inefficient and chatty communication that will not work well in distributed environments. The challenges of correctly designing a distributed system are so well known that there is even a canon known asdistributed computing fallacieswhich lists the assumptions developers often make when moving from monolithic to distributed projects.
There is not one solution, but several. One solution is to isolate the business microservices as much as possible. It then uses asynchronous communication between internal microservices and replaces the verbose communication that is typical of in-process communication between objects with more verbose communication. You can do this by grouping calls and returning data that aggregates the results of multiple internal customer calls.
A microservices-based application is a distributed system running across multiple processes or services, often even multiple servers or hosts. Each service instance is typically a process. Therefore, services must interact using an interprocess communication protocol such as HTTP, AMQP, or a binary protocol such as TCP, depending on the nature of each service.
The microservices community promotes the philosophy of "smart endpoints and stupid drives" This slogan encourages a design that is as decoupled as possible between microservices and as cohesive as possible within a single microservice. As explained above, each microservice has its own data and its own domain logic. But the microservices that make up a peer-to-peer Applications are often choreographed simply using RESTful communications instead of complex protocols like WS-* and flexible event-based communications instead of centralized business process orchestrators.
Two commonly used protocols are HTTP Request/Response with Resource API (especially when performing queries) and Lightweight Asynchronous Messaging when communicating updates across multiple microservices. These are explained in more detail in the following sections.
Types of communication
The client and the services can communicate through different types of communication, each one aimed at a different scenario and objectives. Initially, this type of communication can be classified into two axes.
The first axis defines whether the protocol is synchronous or asynchronous:
synchronous protocol. HTTP is a synchronous protocol. The client sends a request and waits for a response from the service. This is independent of client code execution, which can be synchronous (thread is blocked) or asynchronous (thread is not blocking and the response will eventually reach a callback). The important point here is that the protocol (HTTP/HTTPS) is synchronous and the client code can only continue its task when it receives the response from the HTTP server.
asynchronous protocol. Other protocols like AMQP (a protocol supported by many operating systems and cloud environments) use asynchronous messaging. Client code or the sender of the message generally does not expect a response. It just sends the message like you would send a message to a RabbitMQ queue or any other message broker.
The second axis defines whether the communication has a single receiver or multiple receivers:
single receiver. Each request must be processed by exactly one receiver or service. An example of such communication is thecommand pattern.(Video) Microservices Communication | Transactionality, HA, Resiliency, Fault tolerance and Reliability
Multiple receivers. Each request can be processed by zero to multiple recipients. This type of communication must be asynchronous. An example is thepublish/subscribemechanism used in patterns such asevent driven architecture. This relies on an event bus or message broker interface propagating data updates across multiple microservices via events; it is usually implemented through a service bus or a similar artifact such asAzure Service Bususingthemes and subscriptions.
A microservices-based application often uses a combination of these communication styles. The most common type is single-receiver communication with a synchronous protocol such as HTTP/HTTPS when a regular Web API HTTP service is invoked. Microservices also often use messaging protocols for asynchronous communication between microservices.
It's good to know about these hubs to gain clarity on possible communication mechanisms, but they're not the important concerns when building microservices. Neither the asynchronous nature of the client thread execution nor the asynchronous nature of the selected protocol are the important points when integrating microservices. WhatesWhat's important is being able to integrate your microservices asynchronously while maintaining the independence of the microservices, as explained in the next section.
Asynchronous integration of microservices reinforces the autonomy of microservices
As mentioned, the key point when building a microservices-based application is how you integrate your microservices. Ideally, you should try to minimize communication between your internal microservices. The less communication between microservices, the better. But in many cases you will need to integrate the microservices in some way. When you need to do this, the critical rule here is that communication between microservices must be asynchronous. This doesn't mean you have to use a specific protocol (eg asynchronous messaging versus synchronous HTTP). It simply means that communication between microservices should be done by just propagating data asynchronously, but try not to rely on other internal microservices as part of the initial service's HTTP request/response operation.
If possible, never rely on synchronous (request/response) communication between multiple microservices, even for queries. The goal of each microservice is to be independent and available to the consuming customer, even if the other services that are part of the end-to-end application are down or unhealthy. If you find that you need to make a call from one microservice to other microservices (such as making an HTTP request for a data query) to provide a response to a client application, you have an architecture that will not be resilient when some of the microservices fail.
Furthermore, having HTTP dependencies between microservices, such as creating long request/response cycles with HTTP request strings, as shown in the first part of Figure 4-15, not only makes your microservices non-autonomous, but also affects their performance as well. that possible. one of the services in this chain is not working well.
The more synchronous dependencies you add between microservices, such as query requests, the worse the overall response time for client applications.
Figure 4-15. Antipatterns and patterns in microservice communication
As shown in the diagram above, synchronous communication creates a "chain" of requests between microservices while serving the client's request. This is an antipattern. In asynchronous communication, microservices use asynchronous messages or http probes to communicate with other microservices, but the client's request is serviced immediately.
If your microservice needs to generate an additional action in another microservice, if possible, do not perform that action synchronously and as part of the original request and response operation of the microservice. Instead, do it asynchronously (using asynchronous messages or integration events, queues, etc.). But, as much as possible, don't call the action synchronously as part of the original synchronous request and response operation.
And finally (and this is where most of the problems when creating microservices arise), if your initial microservice needs data that originally belongs to other microservices, don't rely on making synchronous requests for that data. Instead, replicate or propagate this data (only the required attributes) into the initial service database using eventual consistency (typically using integration events as explained in the next sections).
As noted above inIdentifying domain model boundaries for each microserviceIn the section, duplicating some data across multiple microservices is not bad design; rather, by doing so, you may translate the data into language or terms specific to that additional domain or limited context. For example, ineShopOnContainers applicationyou have a microservice called
Identity APIwhich handles most of the user's data with an entity called
from user. However, when you need to store data about the user within the
cleanmicroservice, it stores it as a different entity called
Buyerentity shares the same identity with the original
from userentity, but may have only a few attributes needed for the
cleandomain, not the entire user profile.
You can use any protocol to communicate and propagate data asynchronously between microservices for eventual consistency. As mentioned, you can use integration events using an event bus or message broker, or you can even use HTTP polling the other services. It doesn't matter. The important rule is not to create synchronous dependencies between your microservices.
The following sections explain the various communication styles that you might consider using in a microservices-based application.
There are many protocols and options you can use for communication depending on the type of communication you want to use. If you are using a request/response based synchronous communication mechanism, protocols like the HTTP and REST approaches are more common, especially if you are publishing your services outside of the Docker host or cluster of microservices. If you are communicating between services internally (within your Docker host or cluster of microservices), you can also use binary format communication mechanisms (such as WCF using TCP and binary format). Alternatively, you can use asynchronous message-based communication mechanisms such as AMQP.
There are also several message formats, such as JSON or XML, or even binary formats, which can be more efficient. If the binary format you choose isn't a standard, it's probably not a good idea to publicly publish your services using that format. You can use a non-standard format for internal communication between your microservices. You can do this when communicating between microservices on your Docker host or cluster of microservices (for example, Docker orchestrators) or for proprietary client applications that communicate with microservices.
Request/response communication with HTTP and REST
When a client uses request/response communication, it sends a request to a service, the service processes the request and returns a response. Request/response communication is especially suited to querying data for a real-time user interface (a live user interface) of client applications. Therefore, in a microservices architecture, you will likely use this communication mechanism for most queries, as shown in Figure 4-16.
Figure 4-16. Using HTTP request/response communication (synchronous or asynchronous)
When a client uses request/response communication, it assumes that the response will arrive in a short amount of time, usually less than a second or a few seconds at most. For delayed responses, you must implement asynchronous communication based onmessage patternsymessaging technologies, which is a different approach that we explain in the next section.
A popular architectural style for request/response communication isREST. This approach is based on and closely related to theHTTPprotocol, which encompasses HTTP verbs such as GET, POST, and PUT. REST is the most widely used architectural communication approach in creating services. You can implement REST services when developing ASP.NET Core Web API services.
There is additional value in using HTTP REST services as your interface definition language. For example, if you useSwagger MetadataTo describe your service's API, you can use tools that generate client stubs that can directly discover and consume your services.
Martin Fowler. Richardson's Maturity ModelA description of the REST model.
PavonearseThe official website.
HTTP-based push and real-time communication
Another possibility (often for purposes other than REST) is real-time and one-to-many communication with higher-level frameworks likeASP.NET SignalRand protocols likeWebSockets.
As Figure 4-17 shows, real-time HTTP communication means you can have server code send content to connected clients as data becomes available, rather than having the server wait for a client to request new ones. Dice.
Figure 4-17. One-to-many real-time asynchronous messaging communication
SignalR is a good way to get real-time communication to send content to clients from a backend server. Since communication is in real time, client applications show changes almost instantly. This is usually handled by a protocol like WebSockets using many WebSockets connections (one per client). A typical example is when a service communicates a change in the score of a sports game to multiple client web applications simultaneously.