This is a design question about the optimal/recommended way of maintaining shared mutable state using Kotlin concurrency primitives. For that, I need to establish the context first, please bear with me.
I’ve a Spring Boot web application that has a gRPC client. Following are the requirements/constraints on the system:
- Messages from the gRPC server could be informational or commands.
- The client has internal state, that may change depending on the commands received from the server. Informational messages don’t affect client state.
- Based on the commands received from the server, the client may generate more messages for the server, or update its internal state, or do both.
- Every client message generated due to a server command must happen after any state changes.
My current design is as follows;
- The client is a Spring bean. It has a reference to its internal state, which is a POJO (or POKO, if you want
)
- Upon receiving a server message, it constructs a command instance and passes the message content and the client state to the command. This happens in a gRPC streaming receiver, which runs on a gRPC thread pool (default).
- The command instance may update the client state, and returns one or messages to the client.
- The client, running within an
Unconfined
dispatcher, puts the messages in a bufferedChannel
, and waits for the next server message. - A
Channel
consumer, running on Kotlin default dispatcher pool, picks up the messages and sends them to the server.
Notice that requirement #4 is satisfied by design steps #3 and #5.
Questions:
- I keep hearing that
Flow
is recommended overChannel
. In this case though, theChannel
needs to stay open throughout the lifecycle of the client, and is very easy to use as an async queue. What benefits, if any, are there for using aFlow
in this case? - I don’t feel particularly good about passing the client state to the commands. Is there a different way of achieving requirement #4 without doing this? (actors, may be)