I'm developing a smoke tests app in Go that tests a number of services (Redis, RabbitMQ, Single Sign-On, etc) that are offered in the marketplace of a CloudFoundry installation at one of our customers. These tests produce simple JSON output that signals what went wrong. Now the customer has asked for a dashboard so the entire organization can check on the health of the platform.
I took some time to come up with a good enough design for this and decided on the following:
- The smoke tests app (Golang) pushes its results to RabbitMQ
- An ASP.NET Core app listens to smoke test results and keeps track of state (the results themselves and when they were received)
- A single page written in Elm that receives status updates via SignalR (web sockets)
Since I have never written anything in Elm and my knowledge of SignalR is a little outdated, I decided to start very simple: a SignalR hub that increments an int every five seconds and sends it to all clients. The number that's received by each client is used to update an Elm view model. In the real world, the int will become the JSON document describing the results of the smoke tests and we build a nice view for it, you get the idea.
All source code for this post can be found here.
The server side of things
First of all, what do things look like on the server and how do we build the application? It will be an ASP.NET Core app so we start with:
dotnet new web
dotnet add package Microsoft.AspNetCore.SignalR -v 1.0.0-alpha2-final
We create an empty ASP.NET Core website and add the latest version of SignalR. Next we need to configure SignalR in our Startup
class:
loading...
The code speaks for itself, I guess. We add SignalR dependencies to the services collection and configure a hub called SmokeHub
which can be reached from the client via the route /smoke
.
On line 15 you can see I add a IHostedService
implementation: CounterHostedService
. A hosted service is an object with a start and a stop method that is managed by the host. This means that when ASP.NET Core starts, it calls the hosted service start method and when ASP.NET Core (gracefully) shuts down, it calls the stop method. In our case, we use it to start a very simple scheduler that increments an integer every five seconds and sends it to all SignalR clients. Here are two posts on implementing your own IHostedService
.
The client side of things
First of all, we need the SignalR client library. You can get it via npm. I added it in the wwwroot/js/lib folder
.
Now let's take a look at the Elm code.
loading...
Let's dissect the code:
- Line 6: we have a model, which is an
Int
that we initialize to1
- Line 13: we have one message type, which is a counter of int
- Line 17: our view takes our model and returns some very simple html, showing the model value
- Line 22: when we receive an update, we simply return the count
- Line 29: we subscribe to counter updates
The question is, where do we receive counter updates from? Elm is a pure functional language. This means that the output of every function in Elm depends only on its arguments, regardless of global and/or local state. Direct communication with Javascript from Elm would break this so that is not allowed. So all interop with the outside world is done through ports.
If we check the Elm code again, you see at line 1 we declare our module with the keyword port
. On line 30 we declare a port that listens to counter updates from Javascript. So now we can plug it all together in our index.html
file:
loading...
Most of the code speaks for itself. On line 22 we invoke the port in our Elm app to pass the updated counter to Elm. Line 25 is a simple test to assure that we can also send message from the client to the SignalR hub.
For completeness' sake, here is the code for the SmokeHub
:
loading...
Note that the Send
method is called by JavaScript clients. It is not the same as the Send
that is called when notifying all clients of a counter update.