
In dieser Mini-Serie schaue ich mir die Building Blocks im Domain-driven Design (DDD) an. Diese Bausteine gehören zum Tactical Design des DDD. Abgrenzend gibt es das Strategic Design, welches sich mit den High-Level-Aspekten der Domäne als Ganzes befasst, wie der Ubiquituos Language, Bounded Contexts und Context Maps. Strategic Design ist der Hands-On-Part im DDD und die Building Blocks stellen konkrete Möglichkeiten dar, um DDD in Code zu gießen.
- Domain-driven Design: Value Object
- Domain-driven Design: Entity, Aggregate und Domain Service
- Domain-driven Design: Factory und Repository
- Domain-driven Design: Domain Event
Dieser dritte Teil der Serie befasst sich mit der Factory und dem Repository.
Factory
Factories sind auch als Entwurfsmuster außerhalb von Domain-driven Design bekannt. Sie erledigen auch hier den gleichen Zweck: Objekte erzeugen. Eine Factory im Domain-driven Design bildet aber auch Fachlichkeit ab und dies ist auch der Grund, warum ich davon abrate, Entities und Aggregates über einen üblichen Konstruktor, in Java etwa mit dem Keyword new, zu erzeugen: Die Erzeugung einer neuen Entity ist eine fachliche Transaktion und sollte mit einem fachlichen Namen versehen sein.
Eine Factory kann darüber hinaus dafür zuständig sein, die neue Entity bei der Instanzierung mit einer Identität zu versorgen. Wie auch im gleichnamigen Entwurfsmuster, kann eine Factory aus dem Domain-driven Design je nach Kontext und Eingabeparametern auch Instanzen unterschiedlicher Klassen erzeugen.
Meiner Meinung nach muss eine Factory nicht zwingend eine eigene Klasse sein. Fällt bei der Erzeung der Entity wenig Arbeit an, so kann auch eine statische Factory-Methode in der gleichen Klasse die Erzeugung übernehmen. Dabei halte ich es für wichtig, dass keine Abhängigkeiten von der Entity zu anderen Klassen entstehen, etwa weil die statische Methode eine ID von einem ID-Vergabe-Service beziehen muss. In dem Fall wäre eine dedizierte Factory-Klasse aus meiner Sicht angebrachter. Wird aber die ID erst bei der Aufnahme der Entity im Repository vergeben und es müssen keine weiteren Informationen bei anderen Services angefragt werden, dann spricht aus meiner Sicht nichts gegen eine statische Factory-Methode in derselben Klasse. In Java kann dies etwa so aussehen:
public class Auftrag {
private Auftrag() {
}
public static Auftrag erzeugen() {
return new Auftrag();
}
...
Repository
Ein Repository verwaltet Aggregates. Ein Aggregate wird durch eine RootEntity repräsentiert, die den Zugriff auf die dem Aggregate untergeordneten Entities bündelt und die Einhaltung der fachlichen Rahmenbedingungen gewährleistet.
Repositories gibt es auch außerhalb von Domain-driven Design. Die spannende Herausforderung besteht meiner Meinung nach darin, dass ein Repository im Domain-driven Design Teil der Domäne ist und diese Domäne komplett fachlich, also ohne technischen Overhead, abgebildet werden sollte.
Das kann bei einigen eingesetzten Technologien mit einem unverhältnismäßig hohem Aufwand verbunden sein, so dass man Kompromisse eingehen muss. Beispielsweise würde ich bei Verwendung von JPA, also der Java Persistance API zum Zugriff auf Objekte in relationalen Datenbanken, die JPA-Entitäten und Repositories als Teil der Domäne betrachten und damit eine technische Verschmutzung der Domäne in Kauf nehmen. Da JPA zum größten Teil über Annotationen abgewickelt werden kann, ist der tatsächliche technische Code sehr überschaubar und das Ergebnis insgesamt flexibler und besser verständlich, als wenn ich ein fachliches und ein technisches Repository hätte, die sich untereinander synchronisieren müssten.
Ein wenig schmerzlich ist die technische Belastung im Falle von JPA dennoch, da sie sich nicht nur auf die Repositories beschränkt, sondern ja gerade auch die verwalteten Aggregates entsprechend annotiert werden müssen. Zudem erfordert es JPA, dass ein Standard-Konstruktor existiert und die verwalteten Felder nicht final sind.
Das folgende Beispiel demonstriert, wie der im vorherigen Code-Beispiel gezeigte Auftrag für JPA erweitert und in einem Repository verwaltet werden kann:
@Transactional
public interface AuftragRepository extends JpaRepository<Auftrag, Long> {
Auftrag findById(Long auftragId);
}
@Entity
@Table(name = "auftraege")
class Auftrag {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "bezeichnung")
private String bezeichnung;
protected Auftrag() {
}
public static Auftrag erzeugen() {
return new Auftrag();
}
...
Es wird deutlich, dass man hier doch ein paar Kompromisse eingehen muss. Den größten Schmerz bereitet mir persönlich der Verzicht auf final. Dennoch erspart man sich eine Menge Programmcode, wenn man nicht das fachliche Repository mit dem technischen außerhalb der Domäne synchronisieren muss, ganz zu schweigen von dem zusätzlichen Fehlerpotenzial, das ein solcher Ansatz noch mit sich bringen würde.
Wird man aber nicht durch ein technisches Framework zu einem solchen Kompromiss gezwungen, würde ich auf jeden Fall versuchen, ein rein fachliches Repository abzubilden, welches z.B. über eine als Interface ins Repository gehängte DataSource aus dem Application Layer Daten nachlädt und auch physikalisch persistiert.