In einem früheren Blog-Post habe ich argumentiert, weshalb Onion Architecture für die Umsetzung von Bounded Contexts nach Domain-driven Design besonders geeignet ist und dabei auch angesprochen, dass diese Umsetzung durch den Einsatz von Stereotypen stark profitieren kann. Stereotypen sollen daher Thema dieses Blog-Post sein.

Stereotypen

Der Begriff „Stereotyp“ stammt ursprünglich aus dem geistes- und sozialwissenschaftlichen Kontext, wird aber spätestens seit UML auch in der Informatik zur generellen Kennzeichnung und Kategorisierung von konkreten Elementen (oder Ausprägungen) anhand eines Meta-Modelles verwendet. Ein Stereotyp hat dabei einen im Kontext eindeutigen Namen, aber - ebenso wichtig - auch eine klar definierte Semantik. Diese Semantik wird durch die Verwendung des Stereotypen auf die konkrete Ausprägung (z.B. eine Klasse) übertragen.

Ist die Semantik eines bestimmten Stereotypen im Team verstanden, erschliesst sich dadurch auch bereits ein weitgehendes Verständnis der Bedeutung der konkreten Ausprägung. Somit trägt dieses „Schubladendenken“ enorm zum gemeinsamen Verständnis bei und führt mit der Zeit zu einer Art „gemeinsamer Architektursprache“ innerhalb des Teams. Dabei ist allerdings wichtig zu sehen, dass die Stereotypen selbst nicht Teil der Ubiquitous Language im Sinne von DDD sein können, da sie keine fachlichen Konzepte, sondern Konzepte aus der Applikationsarchitektur bezeichnen.

Taktische Muster in DDD und Stereotypen

In DDD existieren neben den strategischen Mustern wie z.B. Bounded Context, Ubiquitous Language oder Context Map auch eine Reihe von taktischen Mustern wie z.B. Aggregate, Repository, Domain Service oder Domain Event. Für eine ausführlichere Diskussion der taktischen DDD Muster empfiehlt sich das frei verfügbare Dokument Domain-driven Design Reference von Eric Evans. Es enthält eine aktualisierte und gut verständliche Übersicht inklusive Ergänzungen seit der Erscheinung des „Blue Book“ von Eric Evans.

Diese taktischen Muster bieten sich als Kandidaten für Stereotypen der Applikationsarchitektur an. Dadurch kann die Bezeichnung und die Semantik der Muster für die Stereotypen übernommen werden. Eine strikte Beschränkung auf die Muster ist in der Praxis jedoch meist nicht sinnvoll, da abhängig von der gewählten Applikationsarchitektur zusätzliche relevante Konzepte existieren. So kennt die Onion Architecture beispielsweise Application Services, welche die Grenze des Domänenmodells repräsentieren und für die Orchestrierung eingesetzt werden, oder Domain Services, welche für die Umsetzung von Geschäftslogik verwendet werden, die keinem Aggregate zugeordnet werden kann.

In vergangenen Projekten hat sich darüber hinaus auch die Definition von projektspezifischen Stereotypen bewährt, da sie helfen, eine präzisere gemeinsame Sprache mit mehr Semantik durch eine feinere Kategorisierung der Elemente innerhalb der Applikationsarchitektur zu schaffen. Ein Beispiel dafür ist ein Stereotyp für Domain Event Handler, die Domain Events verarbeiten. Wichtig ist dabei allerdings, auch für eigene Stereotypen eine präzise Semantik zu definieren, welche zudem nicht im Konflikt zu bestehenden Stereotypen stehen sollte. Auch die „grosszügige Uminterpretation“ von bestehenden Stereotypen empfiehlt sich nicht, da damit Missverständnisse entstehen können, wenn sich in Zukunft neue Entwickler z.B. mit DDD-Verständnis mit der Code-Basis beschäftigen.

Umsetzung und Verwendung von Stereotypen im Code

Stereotypen lassen sich auf unterschiedliche Weise im Code umsetzen und verwenden. In objekt-orientierten Sprachen (hier beispielhaft Java) bilden sich die taktischen Muster aus DDD bzw. die entsprechenden Stereotypen typischerweise auf Klassen ab bzw. werden dort angewendet: eine konkrete Klasse ist eine Ausprägung eines bestimmten Stereotypen. Allerdings muss dies nicht zwingend so sein, da sich einzelne Stereotypen durchaus auch auf Methoden oder Felder anwenden lassen, wie später gezeigt wird.

Stereotypen via Klassennamen

Eine technisch einfache Variante ist die Abbildung von Stereotypen über den Namen der Klasse der konkreten Ausprägung:

class CustomerAggregate {
  // ...  
}

class CalculateCustomerDiscountDomainService {
  // ...
}

Dieser Ansatz hat allerdings eine Reihe von Nachteilen:

Als Argument für diesen Ansatz kann neben der technischen Einfachheit aufgeführt werden, dass durch die direkte Abbildung des Stereotypen im Klassennamen eine höhere Chance besteht, die Bedeutung der Klasse auch effektiv auf die Semantik des Stereotypen zu beschränken - eine einzelne Klasse unscharf mehreren Stereotypen zuzuweisen, wird schon alleine aufgrund der Problematik für das Finden eines sinnvollen Namens erschwert.

Stereotypen via Basisklassen

Eine zweite Möglichkeit für die Abbldung von Stereotypen stellen Basisklassen dar:

abstract class Aggregate {
  // ...
}

class Customer extends Aggregate {
  // ...  
}

abstract class DomainService {
  // ...  
}

class CalculateCustomerDiscount extends DomainService {
  // ...
}

Abgesehen vom kürzeren und auf die Verantwortlichkeit fokussierten Klassennamen wird dadurch der Stereotyp Teil des verfügbaren Typen in Java und kann bei Bedarf auch entsprechend gewinnbringend eingesetzt werden: insbesondere in der Infrasktruktur einer Onion Architecture kann es interessant sein, direkt mit der Abstraktion der Stereotypen zu arbeiten und z.B. in einer Basisklasse für die Implementation von Repositories mit Aggregate als Schranke („Bound“) eines Typ-Parameters zu arbeiten und so zusätzliche Unterstützung durch den Compiler zu erhalten:

abstract class BaseRepository<A extends Aggregate> {

  abstract void add(A aggregate);

}

class CustomerRepository extends BaseRepository<Customer> {

  @Override
  void add(Customer customer) {
    // ...
  }

}

Allerdings besteht dadurch die Gefahr, dass die Basisklasse des Stereotyps direkt im fachlichen Code verwendet wird, was wiederum zu einer Verschmutzung der fachlichen Sprache im Code führt.

Durch die Abbildung als vollwertige Klasse in Java können Stereotypen so auch mit Funktionalität erweitert werden, welche für alle Ausprägungen zur Verfügung stehen sollen. So kann es interessant sein, in der Basisklasse für den Stereotypen für Aggregates auch direkt abzubilden, dass Gleichheit basierend auf der fachlichen Identität bestimmt wird - was durchaus legitim ist, da es ja auch der Semantik von Aggregates entspricht. Jedoch ist der Grat zur Erweiterung der Basisklasse aus „Bequemlichkeit“ oder um rein technische Aspekte schmal: allzu schnell findet sich in derselben Basisklasse auch ein Feld für die technische Datenbank-Id wieder, welche aus fachlicher Sicht keine Relevanz hat.

Stereotypen als Annotationen

In Java bieten sich auch Annotationen an, um Stereotypen abzubilden:

@interface Aggregate {
  // ...  
}

@Aggregate
class Customer {
  // ...  
}

@interface DomainService {
  // ...  
}

@DomainService
class CalculateCustomerDiscount {
  // ...  
}

Dieser Ansatz ist auch in verschiedenen technischen Frameworks zu finden: so kennt JPA annotationsbasierte Stereotypen unter anderem für Entities (@Entity), Beziehungen zwischen Entities (@OneToMany) oder für Identitätsfelder (@Id), Spring kennt Stereotypen als Annotationen z.B. für Repositories (@Repository, wenn auch nicht direkt im Sinne von DDD) oder für Services (@Service).

Die Abbildung von Stereotypen als Annotation bringt verschiedene Vorteile mit sich:

@DomainService
public class CalculateCustomerDiscount {

  @DomainEventHandler
  void recalculateDiscount(OrderPlaced event) {
    // ...    
  }

}
@Aggregate
class Customer {

  @AggregateId
  private final CustomerId customerId;

  // ...

}

Der grösste Nachteil von annotationsbasierten Stereotypen ist wohl die Tatsache, dass sie nicht direkt im Typssystem verwendet werden können, da Java leider kein annotated-with Schlüsselwort im Sinne von extends oder super kennt. Dadurch ist es nicht möglich, mit annotationsbasierten Stereotypen auch von zusätzlicher Compiler-Unterstützung wie bei Basisklassen zu profitieren. Auch können Annotationen selbst kein Verhalten implementieren, so dass keine Funktionalität direkt in Stereotypen abgebildet werden kann. Viele dieser Anforderungen lassen sich jedoch durch den Einsatz von AOP (z.B. mittels AspectJ oder Spring AOP) als Aspekt zu Compile-, Load- oder Run-Time realisieren.

Fazit: Vorteile von Stereotypen

Stereotypen erlauben es, im Projekt eine explizite Sprache für die Elemente der Applikationsarchitektur zu entwerfen, welche danach verwendet werden kann, um den einzelnen Komponenten der Code-Basis eine klar definierte Semantik zuzuweisen. Diese bewusste Entscheidung fördert erfahrungsgemäss das Single Responsibility Principle, da die Bedeutung z.B. einer Klasse explizit beschrieben ist und somit Abweichungen oder unpassende Erweiterungen bei Reviews eher herausstechen.

Die entstehende gemeinsame Architektursprache fördert das gemeinsame Verständnis der Applikationsarchitektur, erleichtert die Diskussion im Team und vereinfacht die Dokumentation des Designs.

Nicht zuletzt können Stereotypen verwendet werden, um die Architekturrichtlinien im Projekt zu beschreiben: auf der Ebene von Stereotypen lässt sich prägnant ausdrücken, welche Ausprägungen eines Stereotypen auf Ausprägungen welcher anderer Stereotypen zugreifen dürfen, oder in welchem Layer bzw. Ring eine Ausprägung eines bestimmten Stereotyps verwendet werden darf.

Ausblick

Die über die Sprache der Stereotypen beschriebenen Architekturrichtlinien lassen sich mit geeigneten Tools formalisieren und automatisiert überprüfen. Dadurch kann eine Architekturgovernance etabliert werden, welche Verletzungen der Architekturrichtlinien automatisiert erkennen und dokumentieren kann. So können unerwünschte Abhängigkeiten bereits zur Build-Time verhindert werden. Wie das realisiert werden kann, wird ein zukünftiger Blog-Post aufzeigen.