Einhaltung von Invarianten mit dem Value Object Pattern

Zentraler Bestandteil eines mit Domain-Driven Design entwickelten Domänenmodells sind Objekte, die große Teile der Geschäftslogik repräsentieren. "heise Developer" zeigt, wie man Domänen-Objekte entwickelt, die keine ungültigen Zustände erreichen können.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 12 Min.
Von
  • Stefan Erras
  • Alexander Neumann
Inhaltsverzeichnis

Zentraler Bestandteil eines mit Domain-Driven Design (DDD) entwickelten Domänenmodells sind Objekte, die große Teile der Geschäftslogik repräsentieren. Beim Implementieren dieser sogenannten Domänenobjekte stellt sich häufig die Frage, wie sie sich validieren lassen. Der Artikel zeigt an einem einfachen Beispiel, wie man mit DDD Objekte entwickelt, die keine ungültigen Zustände erreichen können.

Um die komplexen Anforderungen an eine Geschäftsanwendung langfristig beherrschen zu können, ist es wichtig, Geschäftsdaten und -logik widerspruchsfrei und strukturiert in einer Applikation abzubilden. Um das zu erreichen, schlägt Eric Evans in seinem Buch "Domain-Driven Design: Tackling Complexity in the Heart of Software" [1] vor, alle fachlichen Aspekte einer Anwendung mit einem Domänenmodell zu implementieren, das sich möglichst frei von den technischen Rahmenbedingungen betrachten und realisieren lässt. Man löst ein fachliches Problem somit nicht auf technischer Ebene, sondern verarbeitet es mit dem Modell der Domäne. Das Hauptaugenmerk bei der Softwareentwicklung ist dabei auf dieses Domänenmodell zu richten.

Da sich die Objektorientierung in vielen Fällen für das Implementieren eines solchen Modells eignet, definiert Evans einige Begriffe und Design Patterns, die seiner Strukturierung beitragen können. Die Anwendung dieser Pattern-Sprache nennt man (in Anlehnung an den Buchtitel) "Domain-Driven Design".

Domänenobjekte sind als Einheit aus Daten und Verhalten zu verstehen, die von außen Befehle erhalten können. Es ist oft sinnvoll, diese Objekte so zu implementieren, dass sie das Ausführen eines Befehls verweigern, wenn dieser zu einem ungültigen Zustand führt. Diese Art der Implementierung hat folgende Vorteile:

  • Man kann alle Objekte im System jederzeit ohne weitere Prüfung verwenden oder an beliebige Subsysteme übertragen.
  • Häufig ist es einfacher und/oder performanter, innerhalb der einzelnen Objekt-Methoden zu überprüfen, ob die Ausführung mit den übergebenen Parametern zu einem ungültigen Zustand führen würde, als zu einem späteren Zeitpunkt den kompletten Zustand eines Objekts auf Gültigkeit zu prüfen (zum Beispiel durch Ausführen einer object.validate()-Methode). Das kommt vor allem zum Tragen, wenn untereinander abhängige Invarianten zu prüfen sind.

Mit Value Object bietet Evans ein Design Pattern an, das den Entwurf solcher Domänenobjekte einfacher gestaltet. Objekte, die diesem Entwurfsmuster entsprechen, haben drei wichtige Eigenschaften:

  • keine Identität: Das heißt, das Objekt ist nur durch seinen Dateninhalt definiert und nicht mit einem eindeutigen Schlüssel versehen. Value Objects gelten als gleich, wenn ihr Inhalt übereinstimmt.
  • Ein Value Object ist nach Erzeugen nicht mehr änderbar: Alle für die Konstruktion des Objekts nötigen Daten übergibt man dem Konstruktor.
  • Value Objects sind immer in einem gültigen Zustand zu erzeugen: Bei der Objekterstellung erfolgt eine Prüfung aller Eingabeparameter. Sind sie ungültig, verweigert die Prüfungslogik die Instanzierung.

Diese Eigenschaften erlauben es, Value Objects nahezu beliebig im System hin und her zu reichen, ohne unerwartete Seiteneffekte befürchten zu müssen. Im Domänenmodell unterstützen Value Objects vor allem die eigentlichen Domänenobjekte bei ihrer Arbeit. Das geschieht beispielsweise, indem man Teilaspekte eines Domänenobjekts mit diesem Design Pattern auslagert.