Simple (yet complete) example of game implementation using CQRS and event sourcing backed by Akka persistence and RabbitMQ. Part 2/3
Welcome to the 2nd part of event-sourced game post series! In case you haven’t yet read previous part, I highly recommend reading it before.
Today we’ll focus on the frontend server part, the one that’ll be responsible for handling user interface interactions as well as backend server communication.
Quick recap of what has been done so far in part 1:
Now it’s time to create an UI that’ll seamlessly combine the two to create decent user experience. Here’s how it will look like:
For this, we’ll use:
The action flow from UI point of view will be as follows:
Looking at these, our Play! server will need to handle:
First, let’s add these routes to our Play! application:
POST ones are for commands and the GET route is for websocket connection. Let’s take a look at the implementation.
Nothing special here, commands are just forwarded to our backend server (with some additional responses mapping).
Websocket route is a little bit more complex:
acceptWithActor takes two type parameters: one for incoming messages (it’s just a
String, since we don’t send anything to the server anyway) and another for outgoing messages, which are
To make it work that way we need to tell Play! how to actually send
GameEvent through WebSocket. It’s done by bringing implicit
FrameFormatter[GameEvent] to the scope.
GameEvents as JSON and use
FrameFormatter.jsonFrame which Play! provides:
What’s next in
acceptWithActor is a method that takes a HTTP
request and an
out (which is used to send messages from server to client) and returns
Props to create WebSocket connection handling
We’re telling it to create one
WebsocketEventPublisher for every WebSocket connection, we also pass
out reference to it, which it can later use to send messages through the connection.
Let’s have a closer look at
What we do, when the actor starts, is create a new queue and bind it to the events from given game.
It’s the last parameter in
arguments which says “hey, I want to receive only events from the game with id ==
Once the queue is bound, we create a
Source out of it, which we attach
Our sink is an
EventSubscriber actor, which will receive every incoming event (or, at this point, rather a message that represents an event).
Once it receives an event, it’ll pass it back to
WebsocketEventPublisher (that’s why we pass
self reference to its
WebsocketEventPublisher will pass it further, to
out reference (which will use
FrameFormatter to serialize the message and send it through WebSocket connection).
EventSubscriber tells streams implementations how many more messages the actor wants to receive.
Here it’s implemented to always be at least 1, meaning we want to receive as much messages as there are. It may not however be always appropriate elsewhere. I don’t want to focus too much on back pressure in this post, so we’ll just stick with this.
Those watchful may say that it doesn’t look good to pass these rather backend-side events directly to the browser. It’s true, usually some additional processing/validation would be added before passing them to WebSocket, but it’s not necassary in our simple application.
Now that we have our backend stuff ready, lets see how can we use it on the frontend.
Let’s start with two Angular services:
Here’s how they both look like:
There’s really nothing special here, I won’t even comment the
command part - these are just http POSTs.
event side, we simply broadcast every incoming message, were the
name parameter of
$broadcast contains an event type so that we can later easily listen for particular game event.
User at given time can see one of three pages (stored in
This variable determines currently shown content (and used controller) with
Each controller handles different set of user actions, let’s examine them:
The first one is
It’s really simple. It just POSTs to create a new game and upon success connects the event service to the event stream, changing currently shown page to
choose_players with corresponding
Which gives user a possibility to specify players count and handles “Start game” button click. It also shows a popover in case of error.
If there were no errors and the game was started,
GameStarted event is handled in the main application:
The game state is stored in the
game variable in
rootScope and is updated according to the events received:
Finally, we have
GameController that handles user inputs when the game is running:
It also listens to game events to update the view accordingly. When the game is finished the “Play Again” button is visible which, upon clicking, redirects the user back to the “create” view.
You can find all the related views in index.scala.html file.
That’s it for the 2nd part. Our game is ready :)
We can now consume events from RabbitMQ and send commands to our backend server. Both functionalities are seamlessly integrated in our modest interface and the distincion is invisible for the user. But the fun is not over yet. In the 3rd part we’ll see how easy it is to grab some statistics from the games played and expose collected data.
As usual, full source code is available on GitHub.