Ihr Browser (Internet Explorer 10/11) ist veraltet. Aktualisieren Sie Ihren Browser für mehr Sicherheit, Geschwindigkeit und den besten Komfort auf dieser Seite.
wolkenkit-boards Logo

Portrait von Matthias Wagler
Matthias Wagler


Boards in the Clouds – Part 3: Building the realtime API

So, we had the idea of digitizing our whiteboards. In the previous part of our series we modeled our domain using an interdisciplinary approach with Domain-Driven Design. This time we’ll turn this model into a realtime API prototype.

But before we dive into some code, we should take a minute to learn about an important architectural aspect of wolkenkit: the separation between writing and reading state.

Writing and Reading

Writing means changing the state of our application and is accomplished by sending a „command“ to our backend. Reading actually means querying the state of our application. Both of these tasks have different needs and requirements.

When changing the state of our application, we want to ensure consistency as much as possible. When we need to read parts of our application’s state, we don’t want to query data from lots of different tables or collections in order to retrieve the data needed for a particular view or screen. Reading the application state should feel as fast and snappy as possible. So basically for every view of our application we should have a perfectly optimized read model to query from. This obviously means lots of duplicated data which is hard to update consistently. See the problem here?

So why not separate these two parts in order to optimize for each of them? Scaling can be handled independently on both sides and is a nice side effect that we gain from this separation. And it can happen according to the specific needs of our application.

Just like Domain Driven-Design, this pattern isn’t new; in fact, quite the opposite: it’s called CQRS – Command and Query Responsibility Segregation – and has been around for quite a while now.

Still we’re left with one important question: how do we keep these two sides in sync?

Using events to sync the write and read side

That’s where the events that we’ve modeled in our previous article come in handy. Until now we haven’t really taken advantage of them. But we can actually use the stream of events emitted from our write model to build up the read model. Think of the read model as an interpretation of the facts that happened inside our domain.

We’re now able to build models that are highly optimized for efficientely reading the state. An additional benefit is that we’re able to transform these events into different kinds of read models e.g. lists, trees or graphs, whatever best fits our application. So let’s move on to translating our model into JavaScript code.

Defining the write model

Looking back at the model we sketched collaboratively, we came up with two different aggregates: the board and the post. Each of them provides a set of commands (the blue post-its) and events (the orange ones).

Let’s start by defining the board, we create a JavaScript file in the writeModel folder of our application called board.js. It contains the following code:

const initialState = { title: undefined, slug: undefined }; const commands = { mount (board, command) { if (command.data.title === undefined) { command.reject('Title is missing.'); return; } board.events.publish('mounted', { title: command.data.title, slug: slugify(command.data.title) }); } }; const events = { mounted (board, command) { board.setState({ title: command.data.title, slug: command.data.slug }) } };

An aggregate is a gatekeeper for changing the state of the application. A command is a simple JavaScript function defined in the commands block of our board, which can be seen as the public API of our board. Once the app is started, wolkenkit creates a REST and a Websocket API for us under the hood that is able to receive the command we’ve just defined. Fortunately for us, we don’t have to deal with the technical details.

In this case we define a command called mount that checks if the client has provided a title, otherwise it rejects the state change. If a title is given the mounted event is published and the data is passed on to it. Our board should later be reachable via a clean URL, that’s why we create a slug for the board and add it as an additional attribute to the event. Finally in the events block, we define how the state of our board will change when a particular event happens. In this case we react to the mounted event and set the title and the slug.

We are now able to change the state of our application. But how how do we read it, or in other words, how do we display a list of our boards? Let’s move on to the read model.

Defining the read model

The read model is responsible for translating the domain events published by the the write model into data structures that can easily be read by clients, in our case a list. So in order to build up a list of boards, we create a JavaScript file called readModel/lists/boards.js, which should list all the currently available boards so that our client is later able to render them.

const projections = { 'collaboration.board.mounted' (boards, event) { boards.add({ title: event.data.title, slug: event.data.slug, timestamp: event.metadata.timestamp }); } };

A read model is just an event listener that is able to subscribe to and act upon the stream of domain events. In this case it only listens for mounted events published by our board aggregate. If this event happens, it adds an entry to the list. Again, wolkenkit will instantly notifiy and update all clients subscribed to this list, so we can focus on transforming the events instead of wrestling with the details of the Websocket implementation.

We’re now be able to write and query the state of our application. Seems like the perfect time to try it out – so let’s go ahead and start our app. To do so, we need the wolkenkit comand line interface (CLI) and Docker for Mac. Once this is set up, we run wolkenkit start and let the CLI take care of the boring details: it packages our application’s source code into lightweight containers and runs them alongside with a couple of infrastructure containers (e.g. database and message queue) inside of our local Docker server.

Using the CLI to start an application.

So now the app is running locally and is serving an HTTPS and Websocket-API. But how do we quickly verify that it is working correctly?

console.wolkenkit.io – Rapid API Prototyping

One possible next step is to test the API we’ve just created which could already be done during a modeling session. However, building a UI that works across different devices is often one of the most challenging tasks of software projects. So we can use a little helper called console.wolkenkit.io to connect to wolkenkit apps, which in our case is running locally on our dev machine.

Just like any other client that uses the wolkenkit SDK it will grab the configuration from the backend and build up a client side API that reflects the model we’ve just defined on the server side. You can use it to issue commands, track the resulting events and read and observe the state of our boards list.

So we’ve built the first parts of our write and read model in JavaScript code. And we’ve also verified that it works using the wolkenkit console. In the next part of our series we’ll talk about how we can take advantage of the wolkenkit client SDK ourselves and build a client and a UI which is able to mount boards and attach post-its to them.