Skip to content

Instantly share code, notes, and snippets.

@rucek
Last active December 12, 2017 13:25
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save rucek/012b3d270e86b871cdc8c4f59e13094f to your computer and use it in GitHub Desktop.
play-scala-websocket-example

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 the hubSink (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 the Source returned by this method is materialized, it returns a Sink as a materialized value. This Sink can be materialized arbitrary many times and each of the materializations will feed the elements into the original Source.

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 a Source as materialized value. This Source can be materialized an arbitrary number of times and each materialization will receive the broadcast elements from the original Sink.

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.

@efrister
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment