DDD & co., part 9: Coding the read model

tl;dr: Mapping domain events to preprocessed read tables is easier than you might think. Essentially, only the functions used to process the events are to be defined.

Since CQRS separates writing and reading within an application, the code which reflects the domain model is only one side of the application. The other side which is responsible for queries is still missing.

As mentioned in the episode on CQRS, a synchronization process is required that maps the domain events stored in the event store to preprocessed read tables and carries out a semantic interpretation of the data.

Similar to the episode From model to code, you can also write code as draft for the read side, which represents the necessary steps. As a programming language, JavaScript is used again, although you can also use any other language, since the concept itself is technology-independent and therefore transferable.

Since the read side can potentially support different data structures, it is obvious to use them as a top level structure. Therefore, you must first create a directory called lists, since lists are probably one of the most common data structures for reading data. This is done within the application directory todomvc.

The read model has no context

Interestingly, the read model does not use a bounded context. This is useful because the read tables mentioned can process domain events from different contexts, which makes it difficult to assign them to a specific context.

Similar to the aggregates, a correspondingly named JavaScript file is created within the lists directory for each list. For example, in the case of the TodoMVC application, this could be a list of all todos. Therefore, todos.js is chosen as file name.

The basic structure of the file is similar to that of an aggregate, but of course other properties are defined in the case of a list:

const fields = {};
const when = {};

module.exports = { fields, when };

The fields property is used to define and initialize the columns or fields of the list, whereas when contains the functions that react to the incoming domain events and update the list.

Creating a task list

In order to function properly, the todos list requires at least three fields, namely a timestamp, the title of the todo and a field that indicates whether the todo has already been completed or not. Since the title may be frequently used later for searching, it is advisable to index the field:

const fields = {
  timestamp: { initialState: 0 },
  title: { initialState: '', fastLookup: true },
  isTickedOff: { initialState: false }
};

Now the individual when handlers are to be implemented. The first step is a function that reacts to a new todo being noted. The corresponding domain event is called noted and is located within the todo aggregate in the context planning. The hull of the handler therefore looks like this:

const when = {
  'planning.todo.noted' (todos, event, mark) {
    // ...
  }
};

The signature is based on the signature of the functions for processing commands and events on aggregates. The contents of the function must now map the occurrence of the noted event to the addition of an entry in the list:

const when = {
  'planning.todo.noted' (todos, event, mark) {
    todos.add({
      id: event.aggregate.id,
      timestamp: event.metadata.timestamp,
      title: event.data.title
    });

    mark.asDone();
  }
};

Since it is to be assumed that an id field will be present in every list, it is possible to initialize it in a standardized way, so that you do not have to take care of it manually.

Editing and ticking off tasks

The other domain events can also be processed in the same way, for example, to edit tasks or tick them off:

const when = {
  // ...

  'planning.todo.edited' (todos, event, mark) {
    todos.update({
      where: { id: event.aggregate.id },
      set: { title: event.data.title }
    });

    mark.asDone();
  },

  'planning.todo.tickedOff' (todos, event, mark) {
    todos.update({
      where: { id: event.aggregate.id },
      set: { isTickedOff: true }
    });

    mark.asDone();
  }
};

The handlers for the domain events that have not yet been processed follow the schema analogously, which means that it is not necessary to list all of them at this point.

How to run the code?

As with aggregates, the code developed so far does not yet represent an executable application. Instead, it is only a description of the read side of an application in JavaScript.

Once again, however, no further code is actually needed, since all other aspects can be added from the outside. This means that this code also remains very close to the domain-oriented modeling and, with certain limitations, can be read and, above all, evaluated by a domain expert.

Twitter Facebook LinkedIn

Golo Roden

Founder, CTO, and managing partner

Since we want to deliver elegant yet simple solutions of high quality for complex problems, we care about details with love: things are done when they are done, and we give them the time they need to mature.