The RPC
pattern and principle exists already for a log time.
There are many ways to make a communication between client and the server and represent it really as a remote procedure call.
Even if Spring Cloud Stream project is aimed for distributes streaming solutions through chain of functions exposed via bindings on a messaging middleware, every single function in this system can be treated exactly as a remote procedure where we could perform a request-reply pattern via messaging exchange.
Such a function is just a simple cloud native microservice where it exposes connections into an input and output binding destinations through which we can send a request and receive reply using respective messaging API for the broker in between.
Therefore, an mRPC
- messaging Remote Procedure Call.
In Spring Integration this request-reply interaction is called a Gateway
.
Since there is no a gateway implementation in Spring Cloud Stream, we are going to try to simulate a distributed request-reply scenario via Spring Integration API where we have all the required instruments still leveraging Spring Cloud Stream components as a first level requirement for our mRPC
concept.
The implementation is like this:
-
We use a Messaging Gateway from Spring Integration as end-user high-level API to send a request and wait from reply. The fact that underneath we perform a Spring Cloud Stream call over bindings is definitely hidden from end-user thanks to Spring Integration flows definitions;
-
Since we are going to perform a network communication via Spring Cloud Stream bindings, we have to ensure that data we transfer is serializable regarding a protocol dictated by the respective messaging middleware. Therefore, for Spring Integration’s
TemporaryReplyChannel
header, which is crucial in request-reply pattern in the gateway implementation, we will rely on aHeaderChannelRegistry
which can store this non-serializableTemporaryReplyChannel
under some generated key representation; -
Now we are able to send a request in Spring Cloud Stream binding, and we do that via
StreamBridge
API. Note that now thisreplyChannel
header as a string representation is going to be sent over the network to remote function alongside with the payload; -
Since the target remote function, we’d like to call, is exposed by Spring Cloud Stream bindings on dedicated endpoints, and it is not aware of our request-reply intentions, we don’t have choice unless have a separate
Consumer
binding in ourmRPC
application. Therefore, we expose aConsumer
binding to listen on the function’s output destination; -
As long as the target Spring Cloud Stream microservice produces output messages with headers as well, we will be able to restore automatically (thanks to Spring Integration functionality) a mentioned before
TemporaryReplyChannel
header from theHeaderChannelRegistry
and correlate this reply back to the gateway request; -
Since our
mRPC
application can be deployed in several instances, we are going to have several parallel consumers on the same reply destination by default (no explicit consumer group for Spring Cloud Stream) and this is good, since we with this distributed, not connected request-reply scenario we have to ensure that reply comes back to the caller. However, according to the anonymous subscriptions, all our Spring Cloud Stream consumers are going to get all the replies. Therefore, we have to filter out those replies which are not for our current instance using aHeaderChannelRegistry
API againstreplyChannel
header name from the received message: ifTemporaryReplyChannel
is not in the current application memory, there is no entry for respective key from the header.
For simplicity of demonstration, this application is doing just a plain to upper case
transformation in the target bound Spring Cloud Stream microservice.
This mRCP
sample is not going to work as is since there is no any binder in the dependencies.
Plus the target Spring Cloud Stream microservice to call is left out of scope for this approach to demonstrate.
However, the test-case brings for us a RabbitMQ binder and starts a Testcontainer for RabbitMQ broker.
In addition, the test dependency includes out-of-the-box spel-function
and payload-converter-function
from Spring Cloud Stream Applications.
A composite byteArrayTextToString|spelFunction
function is exposed as a Spring Cloud Stream microservice on the upper-case.input
and upper-case.output
destinations.
The mRPC
uses those destinations for its request-reply implementation via StreamBridge
and Consumer
binding.