How to update mutable state in a gRPC client app?

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:

  1. Messages from the gRPC server could be informational or commands.
  2. The client has internal state, that may change depending on the commands received from the server. Informational messages don’t affect client state.
  3. 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.
  4. Every client message generated due to a server command must happen after any state changes.

My current design is as follows;

  1. The client is a Spring bean. It has a reference to its internal state, which is a POJO (or POKO, if you want :slight_smile:)
  2. 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).
  3. The command instance may update the client state, and returns one or messages to the client.
  4. The client, running within an Unconfined dispatcher, puts the messages in a buffered Channel, and waits for the next server message.
  5. 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:

  1. I keep hearing that Flow is recommended over Channel. In this case though, the Channel 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 a Flow in this case?
  2. 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)
  1. Flow may have a little bit more overhead and latency, but prevents you from accidentally leaking channels.

  2. Many important nuances are not entirely clear from your description, it may be easier to just show the code.

Actors will allow you to have no shared state at all (encapsulate all pieces of state in the corresponding actors). Downsides:

  • you cant get coherent view of all states at once, they are all independent by design
  • additional overheads of passing immutable messages

@wdoker The question is from a work project, it’ll not be a small task to create a make-believe app for that (gRPC server, gRPC client, back and forth between them). I didn’t find a single example in the docs or on the internet about long-lived flows (apparently everyone in the world uses them for passing around integers and calculating Fibonacci numbers), or for managing shared mutable state using actors (again, except for keeping integer counters, apparently very common ask in real life).