Schubladendenken - aber konstruktiv

Verwendung von Stereotypen im Code als Basis für ein gemeinsames Architekturverständnis - und mehr

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:

  • Er führt zu längeren und komplizierteren Klassennamen, welche neben der eigentlichen Verantwortlichkeit der Klasse auch noch den Stereotypen repräsentieren müssen. Aussagekräftige Klassennamen zu finden, ist bekanntlich bereits ohne Stereotypen eine Herausforderung.
  • Die fachliche Sprache im Code wird verschmutzt, da der Stereotyp im Klassennamen an vielen Stellen im Code auftaucht (z.B. als Typ oder Namen von Variablen und Parametern), aber keine fachliche Bedeutung kommuniziert. Aus fachlicher Sicht ist die Bezeichnung „Customer“ relevant, nicht „Customer Aggregate“.
  • Der Stereotyp kann über die Kommunikation der Semantik hinaus nicht einfach für weitere Zwecke benutzt werden - so ist es z.B. nicht ohne Weiteres möglich, innerhalb der Code-Basis alle Ausprägungen eines bestimmten Stereotypen zu identifizieren bzw. zu selektieren.

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:

  • Annotationen sind nicht invasiv in die jeweils annotierte Klasse. Sie treten bei der Verwendung der annotierten Klassen im fachlichen Code nirgends auf. Annotationen sind ausdrücklich für die „Beschreibung“ von Code-Konstrukten eingeführt worden und heben den Dokumentationscharakter von Stereotypen hervor.
  • Annotationen sind orthogonal zur Klassenhierarchie und erlauben somit nach wie vor die Ableitung von einer anderweitigen Basisklasse, falls dies aus fachlicher Sicht sinnvoll ist (z.B. bei mehreren spezialisierten Ausprägungen eines fachlichen Konzeptes mit gemeinsamen Eigenschaften).
  • Annotationsbasierte Stereotypen können nicht nur auf Klassenebene, sondern auch auf der Ebene von Feldern oder Methoden eingesetzt werden. Dies schränkt die Umsetzung weniger ein, da z.B. ein Domain Event Handler direkt auf der Ebene einer Methode abgebildet werden kann oder ein Feld als Identität eines Aggregate ausgezeichnet werden kann:
@DomainService
public class CalculateCustomerDiscount {

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

}
@Aggregate
class Customer {

  @AggregateId
  private final CustomerId customerId;

  // ...

}
  • Annotationen können zudem zur Laufzeit verfügbar gemacht werden (via @Retention(RUNTIME)) und somit verwendet werden, um zusätzliches Verhalten an Ausprägungen eines bestimmten Stereotypen zu binden. Typische Beispiele sind Transaktionsmanagement, Autorisierung oder Fehlerbehandlung bzw. Logging. Dadurch können derartige Querschnittsaspekte direkt unter Verwendung der gemeinsamen Architektursprache konfiguriert werden.
  • Meta-Annotationen erlauben die weitere Kategorisierung von Stereotypen selbst wiederum durch Stereotypen, z.B. zur Auszeichnung aller Stereotypen des Domain Models oder der Infrastruktur.

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.

TAGS

Comments

Please accept our cookie agreement to see full comments functionality. Read more