Deciding between Flows and Channels

I’ve read everything I can find comparing Flows and Channels (including @elizarov blog posts on the topic) and I mostly understand the differences and best practices. However I am having a very difficult time deciding what to use in a particular situation I’ve encountered. Channels (primarily ConflatedBroadcastChannels) are at the center of my library. I’m primarily using them to represent a stream of data from a sensor and I always need to be able to access the most recent value. Almost all my considerations point to BroadcastChannels being the correct solution (I need thread safety, the streams are hot / continuous, and I need to allow multiple subscribers.) The only thing that makes me think Flows should be a part of this is there are many situations where the value of the sensor can cascade through several conversions.

For example I might have a voltmeter that constantly sends out voltages then I have a thermocouple which takes the voltages and converts them to temperatures. If my API guarantees exposure to a channel I need to launch a new coroutine for every conversion so I can receive -> convert -> send over new Channel whereas with Flows I could do this lazily by at each step just calling map {} or some other transformation function. But then I would have to switch to an entirely Flow based API when Channels really seem more suitable for most aspects of this.

So I guess I’m looking for information or opinions on:

  1. Is it possible to do the conversion using Channels without launching an intermediate coroutine just for that conversion (i.e. do the conversion at the final consumption point)?
  2. Should I even be worried about having lots of intermediate conversion coroutines running or is that not really an issue?
  3. Any other considerations I should be taking into account when deciding between a Flow based and Channel based API?
1 Like

Short answer: use Flow for public API

Also probably in many cases, you can replace internal implementation that based on Channel with channelFlow builder, that also provides thread safety, but also lazy and easier to manage lifecycle, you essentially encapsulate source of data to flow instead of updating global state by sending to broadcast channel. Tho, for now, there is no way to make such flow shared (have multiple subscribers like BroadcastChannel), but it will be available in the future

2 Likes

@gildor I have a somewhat similar setup just that I am getting data from a kinesis stream. So right now each step is in its own coroutine and the value is being passed on throgh a channel. So I basically have kinesis client -> transformation -> storeToDb -> readExtraDataFromDb -> furtherTransform -> storeToAnotherDb

So some of the steps are a little bit different but it feels to me like more or less the same problem. How come you would recommend using flow instead of channels in this case? And if one uses channelFlow i.e. introduces channels into the mix how is that very different or better when you are recieving a hot stream of data?

The only practical difference I can see right now is if I wrap the kinesis client with a channelFlow is that it will not be launched until the terminal operation is reached. So I guess in this case the flow is basically data stream itself, where-as the channel has to be passed to the client as well as to the transformer. But that in itself does only seem like a minor gain to me. Is there something else I am missing?

That aside I do want to convert the app to use Flows just for experimentation sake, but on a high level I can not see how this will be much better. Also conceptually I wanted each step in its own coroutine, because it would allow to support fanOut/fanIn for the different steps as they might have different scaling needs and they could be further extended to be like actors and being able to restart each step individually on an exception.