The issue of inter-service communication is common as microservices cannot function independently. Often, one service needs data from another to call business logic or provide data to the customer. In a typical microservices design, each service has its own database, making it necessary to have a mechanism for connecting and retrieving information. This article will discuss various communication methods, including direct service calls, embedded program calls, and local projections.
Introduction to Microservices Communication Patterns
Microservices can communicate with each other using synchronous (request-response) or asynchronous (event-driven) communication patterns. Synchronous communication involves one service waiting for a response from another service before proceeding, while asynchronous communication allows services to continue processing without waiting for a response. The choice of communication pattern depends on the requirements and constraints of the system being designed.
Direct service calls
It is straightforward to obtain information from a service. Most services have APIs that can be utilized for this purpose.
One service can request data from another using methods such as gRPC, HTTP, or others. The second service will retrieve the database data, potentially modify it, and return it in the specified format. This approach involves synchronous communication and may seem simple, but it can sometimes fail.
Coupling is a crucial aspect to consider when dealing with microservices communication. When two services exchange data and time, temporal coupling occurs when one service must wait for another's response before continuing its operations. This means that even if Service 1 is functioning properly, it may not be able to process requests if Service 2 is unavailable. Similarly, Service 3 may not be able to handle Service 1's requests if it calls Service 3. These types of failures are referred to as "cascading failures" and should be avoided to prevent system disruption. Although developers have created several interesting patterns such as the Circuit Breaker, it only solves a portion of the problem. The major issue with synchronous communication is temporal coupling, which is challenging to eliminate.
API versioning is another important aspect to consider. With numerous suggestions for proper versioning, such as Semantic Versioning, URL Versioning, Headers, and others, it can be a challenge to maintain backward compatibility when deploying new microservices. To prevent breaking clients, it is important to preserve the newest API version and the one before it, or possibly even 10 versions before it. Keeping a growing service backward compatible is a challenging task, and it is necessary to ensure that nothing fails when an API version changes. Automated testing can help in this process, and Contract Testing is a specific technology that can be utilized for this purpose.
Pros and Cons of Direct Service Calls
- Data is current.
- Data-owning microservices can add logic before sending answers.
- Temporal coupling needs circuit breakers and service locating, making it difficult to test.
Embedded program call
In this approach, the data team is responsible for creating and maintaining a shared library, package, module, or other equivalent tools and distributing them with the other services. This shared library can provide read-only access to a database and include some logic for data retrieval and assembly. The microservices that require access to data from other services can utilize this shared library to retrieve the required information without direct calls to the external services.
Pros and Cons of Embedded Program Call
- You don't require hardware or circuit breakers to get up-to-date data.
- Your client library's data retrieval logic may be simpler to start with.
- Possible scheme issues require secure library versioning and dependency management technologies.
- All client library copies use database credentials.
A service may also be asked for data that it does not own. This may occur during a search or a table join using the data from the service. The service typically only provides one method to collect data and one format, even though there are various ways to change the data. If the format, data retrieval options, or data structure do not work for the service, building a local data projection can solve these problems.
In this approach, Service 2's data is copied and stored in Service 1's database. The copy can be stored in either the same file or a new one. Service 1 can access the data by making requests exclusively to its own database, and if Service 2 or its database is not available, it will not affect Service 1's functionality.
Transmission delays are a clear risk, as the data may have changed before the request. Copying database changes is another issue, but replication, message brokers, and Change-Data-Capture tools can provide assistance. The setup and maintenance process will be more complicated.
Pros and Cons of Local Projections
- extra data layout options
- The service is unrelated. The service simply needs local database data. The service can join, map-reduce, etc. its local database data.
- simple tests
- spread slowly, ultimate uniformity needed
- more disk space to make and maintain accurate changes data duplication procedure
Four options are available for microservices communication: UI composition, direct service calls, embedded program calls, and local projections. The choice between them depends on the job requirements and level of difficulty. "Local projection" is a rare choice and is only worth considering in exceptional circumstances where data layout or joining is required.
When deciding between an integrated library and direct calls, it is recommended to consider the size of the project and the size of the team. An integrated library is ideal for small projects with a single repository, while direct calls are better suited for large projects with hundreds of microservices created using different tools. UI composition can be a viable option in certain scenarios, but it may not be sufficient for gathering all required data and can have limitations in terms of client-side logic implementation.