Domain-driven Design erklärt
Seite 3: Aggregates
Aggregates als Konsistenzgrenzen
In beiden Fällen muss irgendetwas das Kommando entgegennehmen und prüfen, ob es zulässig ist. Nur dann darf daraus anschließend das passende Ereignis erzeugt werden.
Die Aufgabe übernimmt ein weiteres Konstrukt von DDD, das sogenannte Aggregate. Dabei handelt es sich um ein Objekt, das Methoden zum Verarbeiten der Kommandos enthält und Ereignisse auslöst. Intern darf ein Aggregate zudem Daten zu einem Geschäftsvorgang speichern, es verfügt folglich über einen Zustand. Er ist erforderlich, um Geschäftsprozesse abbilden zu können, die im Lauf der Zeit mehr als ein Kommando umfassen.
Wichtig für die Definition der Aggregates ist, dass sie eine Konsistenzgrenze darstellen. Das Objekt muss garantieren, dass sein Zustand jederzeit konsistent ist – vor einem Kommando ebenso wie danach. Die Anforderung besteht allerdings lediglich für jedes Objekt individuell, nicht übergreifend für alle oder zumindest unterschiedliche Objekte. Rein technisch stellt ein Aggregate somit eine Transaktionsgrenze dar. Transaktionen dienen dabei aber nur als Mittel, um Invarianten und Konsistenz zu garantieren.
An der Stelle zeigt sich deutlich, warum DDD und CQRS beziehungsweise DDD und Event Sourcing so gut zueinander passen. Will man die Beziehung der Konzepte auf zwei einfache Formeln reduzieren, lieĂźe sich sagen:
DDD + Event Sourcing = Ereignisse
DDD + CQRS = Kommandos
Identität und Gleichheit
Rein technisch ist die Aussage, ein Aggregate ist ein Objekt, nicht ganz korrekt. Genau genommen kann es sich um ein einzelnes Objekt handeln, es kann aber auch ein ganzer Verbund sein. In dem Fall muss das Aggregate die Konsistenz nicht nur fĂĽr ein Objekt garantieren, sondern fĂĽr den gesamten Verbund.
Ein solcher enthält meist zwei Arten von Objekten: Entities und Value Objects. Deren primäres Unterscheidungsmerkmal ist die Frage, ob sie über eine eigene Identität verfügen oder nicht. Entities besitzen eine solche und sind daher per Definition selbst dann unterschiedlich, wenn sie den gleichen Zustand kapseln. Value Objects verfügen hingegen nicht über eine Identität und gelten daher als gleich, wenn sie die gleichen Werte enthalten. Sie sind daher häufig als unveränderliche Konstrukte realisiert.
Nicht alle Programmiersprachen unterstützen das Konzept der Unveränderlichkeit nativ, weshalb Entwickler es im Zweifelsfall nachbilden müssen. Dazu genügt es, auf Methoden zu verzichten, die den Zustand des zugehörigen Objekts verändern. Stattdessen geben sie eine neue Instanz zurück, wie das folgende, in JavaScript geschriebene Beispiel zeigt:
'use strict';
const Probability = function (probability) {
if ((probability < 0) || (probability > 1)) {
throw new Error('Invalid probability.');
}
this.value = probability;
this.equals = function (other) {
const epsilon = 0.01;
return Math.abs(this.value - other.value) < epsilon;
};
this.inverse = function () {
return new Probability(1 - this.value);
};
this.combined = function (other) {
return new Probability(this.value * other.value);
};
this.either = function (other) {
return new Probability(
(this.value + other.value) - (this.value * other.value)
);
};
};
module.exports = Probability;
Dass es sich dabei um ein Value Object handelt, lässt sich leicht an der Funktion equals erkennen. Sie erkennt die zwei Instanzen dann als gleich, wenn sie den gleichen Wert aufweisen.
Den Zugriff auf Aggregates kontrollieren
Falls ein Aggregate ein Verbund aus mehreren Entities und Value Objects ist, stellt sich die Frage, wie sich die Konsistenz innerhalb des gesamten Verbunds sicherstellen lässt. Dazu legt man ein Entity innerhalb des Aggregates als sogenannte Aggregate Root fest. Sie ist sozusagen das primäre Objekt des Verbunds. Rein technisch ist die Aggregate Root ein Entity-Objekt wie jedes andere. Ihre herausragende Stellung bezieht sie aus der Tatsache, dass sie die einzige Schnittstelle nach Außen darstellt.
Der direkte Zugriff auf die Entities und Value Objects innerhalb eines Aggregates ist verboten und darf ausschließlich über die Aggregate Root erfolgen. Ihr obliegt die Aufgabe, die anderen Objekte derart zu verändern, dass am Ende wieder ein konsistenter Zustand vorliegt.