Here's a simple diagram to start with.
There's indeed a single WS flow created and materialized for every WS connection, i.e. for every browser window/tab. This happens when the UserActor
receives WatchStocks
- the created flow is then passed back to the HomeController
.
Now, a WS flow consists of two endpoints: a sink which accepts the data that comes from the browser and a source which emits the data that is sent to the browser. So, to create a WS flow you need to have those two and combine them with Flow.fromSinkAndSourceCoupled
.
In this example, those endpoints are:
- the sink is the
jsonSink
, which accepts a stock name and, in turn, creates and runs a source with some random values for this stock (the purple ones in the diagram); the source is then attached to thehubSink
(the yellow part in the diagram) - the source is the
hubSource
(the green part in the diagram), which emits all values of all the stock
As per the docs, MergeHub.source
creates a source (MHS) which is then connected to the sink (BHS) created by BroadcastHub.sink
. Since those get connected with toMat(...)(Keep.both)
, both materialized values cerated by those two will be returned as a tuple. Since the materialized value of MergeHub.source
is a Sink
and the materialized value of BroadcastHub.sink
is a Source
, the type of the tuple is going to be (Sink[...], Source[...])
, which is exactly what the val (hubSink, hubSource)
in line 37 is. And since we run this small graph immediately in line 39, it's when the MHS and BHS get materialized.
The MergeHub.source
docs say that:
Creates a
Source
that emits elements merged from a dynamic set of producers. After theSource
returned by this method is materialized, it returns aSink
as a materialized value. ThisSink
can be materialized arbitrary many times and each of the materializations will feed the elements into the originalSource
.
So when is the returned hubSink
materialized? Since it's a part of the stock graph defined in line 125, it's materialized every time this graph is run (line 133), i.e. every time a new stock is added (when addStock
is called). And addStock
is called whenever the jsonSink
receives a new stock symbol from the browser. Then, as per the docs excerpt above, all the independent graphs - one for each stock - feed the stock values via their materializations of hubSink
to the MergeHub
which emits them via MHS. MHS, in turn, is connected to BHS, which feeds the values to the BroadcastHub
The BroadcastHub.sink
docs say that:
After the
Sink
returned by this method is materialized, it returns aSource
as materialized value. ThisSource
can be materialized an arbitrary number of times and each materialization will receive the broadcast elements from the originalSink
.
When is the returned hubSource
materialized? Since it's the "output" part of the WS flow, it's materialized every time a new WS connection is opened. Then, the materialized hubSource
is going to emit the stock values that are fed to the BroadcastHub
to the WS client (the browser).
To sum up - when you just run the example and open the page in a single browser window/tab, you're going to have three materializations of the hubSink
- one for every example stock - and a single materialization of the hubSource
- for the single WS connection from the browser.
If you add a new stock symbol in the browser, it's going to be sent through the WS to the jsonSink
, and, in turn, a new stock graph is going to be run with a new materialization of the hubSink
.
If you open the page in another window/tab, a new WS connection is going to be created, and, in turn, a new materialization of the hubSource
.
Hi @rucek. I'm starting to understand, though it's not 100% clear yet.
Practical question: how would you architect it so that you can - instead of streaming stock prices via the streams - send simple responses on requests that come in via WS? Example would be if I pass a command "getStocks" to the WS, and want to get a list of all stock symbols that are subscribed.
The current architecture works fine on sending individual stock prices, but I guess I am wondering how I could expand on this to handle different, generic requests that don't stream data continuously, but at once (like "getStocks", or "getBalance", or "getUserData", something like that)
My thought is to create a new source as a actorRef that I connect to the hubSink in a new flow, and then other actors within my actor system can send JsValues to the actorRef and that will automatically publish via the WS as well.
Do you think something like that makes sense?