DDD & Co., Teil 9: Code für das Read Model

the next big thing  –  0 Kommentare

In einer der vergangenen Folgen ist bereits Code entstanden, der den Entwurf auf der Basis von Kommandos und fachlichen Ereignissen widerspiegelt. Nachdem nun auch die Konzepte CQRS und Eventual Consistency bekannt sind, ist es an der Zeit, die Leseseite vorzubereiten.

Da CQRS das Schreiben und Lesen innerhalb einer Anwendung trennt, macht der Code, der den fachlichen Entwurf auf Basis von Kommandos und fachlichen Ereignissen widerspiegelt, lediglich die eine Seite der Anwendung aus. Die andere Seite, die für Abfragen zuständig ist, fehlt noch.

Wie in der Folge zu CQRS erwähnt, benötigt man einen Synchronisationsprozess, der die neu im Event Store abgelegten fachlichen Ereignisse auf zuvor berechnete Lesetabellen abbildet und auf dem Weg eine semantische Interpretation der Daten durchführt.

Analog zur Folge "Vom Modell zum Code" lässt sich auch für die Leseseite Code als Entwurf schreiben, der die notwendigen Schritte abbildet. Als Programmiersprache kommt wiederum JavaScript zum Zug, wobei man natürlich auch jede andere Sprache verwenden kann, da das Konzept an sich technologieunabhängig und daher übertragbar ist.

Da die Leseseite potenziell verschiedene Datenstrukturen unterstützen kann, liegt es nahe, sie als oberste Ebene der Gliederung zu verwenden. Daher ist zunächst ein Verzeichnis namens lists anzulegen, da Listen vermutlich eine der häufigsten Datenstrukturen zum Lesen von Daten darstellen. Das erfolgt innerhalb des Anwendungsverzeichnisses todomvc.

Das Lesemodell hat keinen Kontext

Interessanterweise verzichtet das Lesemodell so auf den Einsatz eines begrenzten Kontexts. Das ist sinnvoll, weil die erwähnten Lesetabellen fachliche Ereignisse aus verschiedenen dieser Kontexte verarbeiten können, was eine Zuordnung zu einem spezifischen Kontext schwierig macht.

Analog zu den Aggregaten legt man innerhalb des Verzeichnisses lists für jede Liste eine entsprechend benannte JavaScript-Datei an. Im Beispiel der TodoMVC-Anwendung könnte es sich dabei beispielsweise um eine Liste aller Aufgaben handeln. Als Dateiname wird daher todos.js gewählt.

Das Grundgerüst der Datei ähnelt dem von Aggregaten, allerdings werden im Fall einer Liste andere Eigenschaften definiert:

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

module.exports = { fields, when };

Die Eigenschaft fields dient dazu, die Spalten beziehungsweise Felder der Liste zu definieren und zu initialisieren, wohingegen when die Funktionen aufnimmt, die auf die eintreffenden fachlichen Ereignisse reagieren und die Liste aktualisieren.

Eine Aufgabenliste erstellen

Um sinnvoll funktionieren zu können, benötigt die Liste todos mindestens drei Felder, nämlich einen Zeitstempel, den Titel der Aufgabe und ein Feld, das angibt, ob die Aufgabe bereits erledigt wurde oder nicht. Da der Titel unter Umständen später häufig zur Suche herangezogen wird, ist es ratsam, das Feld zu indexieren:

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

Nun sind die einzelnen when-Handler zu implementieren. Den Anfang macht eine Funktion, die darauf reagiert, dass eine neue Aufgabe notiert wurde. Das zugehörige fachliche Ereignis heißt noted und befindet sich am Aggregat todo im Kontext planning. Der Rumpf des Handlers sieht daher wie folgt aus:

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

Die Signatur orientiert sich dabei an der Signatur der Funktionen zum Verarbeiten von Kommandos und Ereignissen an Aggregaten. Der Inhalt der Funktion muss nun das Eintreten des noted-Ereignisses auf das Hinzufügen eines Eintrags in der Liste abbilden:

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

mark.asDone();
}
};

Da davon auszugehen ist, dass ein id-Feld in jeder Liste vorhanden sein wird, kann man dessen Initialisierung auf standardisiertem Weg erledigen, sodass man sich darum nicht von Hand kümmern muss.

Aufgaben aktualisieren, abhaken, …

Auf dem gleichen Weg lassen sich auch die übrigen fachlichen Ereignisse verarbeiten, beispielsweise um Aufgaben zu aktualisieren oder sie abzuhaken:

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();
}
};

Die Handler für die noch nicht verarbeiteten fachlichen Ereignisse folgen dem Schema analog, weshalb es an der Stelle müßig ist, alle aufzulisten.

Den Code ausführen?

Wie auch bei den Aggregaten stellt der bislang erarbeitete Code noch keine ausführbare Anwendung dar. Stattdessen handelt es sich dabei zunächst nur um eine Beschreibung der Leseseite einer Anwendung in JavaScript.

Erneut wird jedoch tatsächlich kein weiterer Code benötigt, da alle weiteren Aspekte wiederum von Außen ergänzt werden können. Damit bleibt auch dieser Code sehr nah an der fachlichen Modellierung und ist, mit gewissen Abstrichen, auch für einen Fachexperten les- und vor allem bewertbar.

tl;dr: Das Abbilden fachlicher Ereignisse auf zuvor berechnete Lesetabellen ist einfacher, als man zunächst meinen könnte. Im Wesentlichen sind lediglich die Funktionen zu definieren, die zum Verarbeiten der Ereignisse dienen.