Mein Blog-Post Schubladendenken - aber konstruktiv hat beschrieben, welchen Wert Stereotypen für eine gemeinsam verstandene Architektursprache im Projekt haben können. Darin wurde auch angesprochen, dass Stereotypen darüber hinaus auch verwendet werden können, um die Architekturrichtlinien im Projekt zu beschreiben. Sind diese Richtlinien einmal definiert, wollen wir auch deren Einhaltung überprüfen und sicherstellen - idealerweise automatisiert. Mit diesem Aspekt der Architecture Governance beschäftigt sich dieser Blog-Post.

Architecture Governance: Kontrolle ist besser …

Architecture Governance beinhaltet mindestens zwei bereits erwähnte Aspekte: die Definition und Dokumentation der Richtlinien und deren Überprüfung inklusive der Berichterstattung bei Verletzungen gegen die Richtlinien.

Die Definition muss dabei in einer Sprache erfolgen, welche sich auf die relevanten Architekturelemente bezieht und dabei aussagt, was erlaubt ist, und was nicht. Nicht überraschend bieten sich hier wiederum die definierten Stereotypen als Repräsentation dieser Architekturelemente an. So lässt sich basierend auf den Stereotypen aussagen, welche Abhängigkeiten erlaubt bzw. verboten sind, in welchen Schichten sich eine Ausprägung eines Stereotypen befinden darf und welchen Eigenschaften eine Ausprägung erfüllen muss. Solche Regeln sollten zudem nicht nur postuliert, sondern auch nachvollziehbar begründet werden.

Bei der Überprüfung der Richtlinien müssen die definierten Regeln dann gegen die effektive Umsetzung in der Code-Basis angewendet und Verletzungen festgestellt werden. Dabei ist es von Vorteil, wenn direkt die Beschriebung der Richtlinien selbst als Input für die Analyse und Bewertung verwendet werden können. Dadurch kann sichergestellt werden, dass keine Abweichungen zwischen der Beschreibung der Regeln und des Inputs für die Analyse entstehen. Zudem soll diese Überprüfung möglichst automatisiert, regelmässig bzw. zeitnah nach Änderungen durchgeführt werden.

jQAssistant

Für beiden Aspekte, sowohl die Definition der Richtlinien als auch deren Überprüfung, existieren eine Vielzahl von frei verfügbaren und kommerziellen Tools. In diesem Blog-Post wollen wir das Tool jQAssistant verwenden. jQAssistant ist open-source und kostenfrei verfügbar. Es kann in unterschiedliche Build-Tools (z.B. Maven oder Gradle) integriert werden. Dadurch kann die Analyse der Richtlinien direkt als Teil der Continuous Integration Pipeline durchgeführt und der Build bei Verletzungen abgebrochen werden.

jQAssistant scannt unterschiedliche Artefakte wie z.B. Java Code oder XML-Dateien und bildet die darin gefundenen Strukturen und Konzepte in einer Graphendatenbank ab, konkret in Neo4j. Regeln lassen sich nun als Abfragen gegen diese Strukturen im Graphen beschreiben (unter Verwendung der Neo4j-Abfragesprache „Cypher“). Liefert eine solche Abfrage ein Resultat, entspricht dies einer Verletzung der jeweiligen Regel.

Darüber hinaus können mit jQAssistant auch eigene höherwertige Konzepte definiert werden, die danach in Regeln verwendet werden können. Dieser Mechanismus kann nun eingesetzt werden, um die Stereotypen aus der Architektursprache als solche Konzept zu formalisieren und diese danach für die Beschreibung der Regeln zu verwenden. Somit sprechen die Regeln ebenfalls wieder die Architektursprache bzw. die Semantik der Stereotypen, wodurch Durchgängigkeit und Verständlichkeit erreicht werden kann.

Die Regeln selbst lassen sich in AsciiDoc oder XML beschreiben und so direkt in die Architekturdokumentation integrieren. Die geforderte Konsistenz zwischen Beschreibung der Regeln und des Inputs für die Analyse ermöglicht jQAssistant dadurch, dass die Architekturdokumentation selbst direkt als Input verwendet werden kann.

Ein einfaches, aber lauffähiges Beispiel ist auf https://github.com/cstettler/onion-jqassistant-sample verfügbar.

Stereotypen als Konzepte abbilden

Bevor ausdrucksstarke Regeln beschrieben werden können, müssen die höherwertigen Konzepte definiert werden. jQAssistant bildet standardmässig bereits sehr viele Elemente aus dem Java Code im Graphen ab, so auch Klassen und ihre jeweiligen Annotationen (Annotationen müssen dabei natürlich mindestens @Retention(CLASS) haben, damit sie im Java Byte Code enthalten sind). Wurden die Stereotypen wie im letzten Blog-Post empfohlen als Annotationen umgesetzt, lässt sich damit nun für jeden Stereotypen ein eigenes Konzept definieren, z.B für Aggregate:

MATCH   (t:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE   a.fqn = "com.github.cstettler...Aggregate"
SET     t:Aggregate
RETURN  t.fqn

Dieser Cypher-Ausdruck selektiert alle Klassen, die mit einer Annotation versehen sind, welche den angegebenen voll-qualifizierten Namen trägt, und setzt auf diesen Klassen das Label Aggregate. Dieses Label kann danach in Regeln verwendet werden.

Das Konzept beschreibt somit ein Element aus der Architektursprache („Aggregate“) mittels Elementen aus der Implementationssprache („Type“, „Annotation“).

Schichten beschreiben

Zusätzlich zu Stereotypen lassen sich auch die gewünschten Ordnungsstrukturen einer Architektur als Konzepte beschreiben. Falls die Umsetzung z.B. mittels einer Onion Architecture erfolgen soll, können die einzelnen Ringe der „Zwiebel“ als Konzepte basierend auf Packages ausgedrückt werden:

MATCH   (t:Type)<-[:CONTAINS]-(p:Package)
WHERE   p.fqn CONTAINS '.domain'
SET     t:DomainRing
RETURN  p.fqn

Dieser Ausdruck selektiert alle Klassen in allen Packages (inkl. Sub-Packages), welche den Domain-Ring repräsentieren, und setzt auf diesen Klassen das Label DomainRing.

Alles am richtigen Platz …

Mit Hilfe dieser beiden höherwertigen Konzepte lässt sich nun eine erste Regel beschreiben: Aggregate müssen sich im Domain-Ring befinden.

MATCH      (a:Aggregate)
WHERE NOT  a:DomainRing
RETURN     a.fqn AS AggregateInWrongRing

Diese Regel selektiert alle Knoten im Graphen, welche mit dem Konzept Aggregate markiert wurden, aber nicht mit dem Konzept DomainRing, und liefert deren voll-qualifizierten Namen zurück. Jeder Treffer entspricht also einem Aggregat, welches im falschen Ring liegt.

Die Regel verwendet dabei in der Formulierung nur noch Begriffe aus der Architektursprache („Aggregate“, „Domain-Ring“), aber nicht mehr aus der Implementationssprache („Klasse“ oder „Package“).

Die Integration von jQAssistant in den Build-Prozess erlaubt es nun, den Build abzubrechen, sobald eine solche Verletzung erkannt wurde. Alternativ lassen sich Verletzungen auch lediglich in einem Report ausweisen, ohne den Build selbst abzubrechen.

Ruf mich (nicht) auf …

Ähnlich wie die Überprüfung der Platzierung von Ausprägungen bestimmter Stereotypen im richtigen Ring lassen sich auch unerlaubte Abhängigkeiten zwischen Stereotypen beschreiben und erkennen.

Sind analog zum Konzept eines Aggregates auch Konzepte z.B. für Application Services definiert (wiederum basierend auf dem jeweiligen Stereotypen), kann exemplarisch folgende Regel verwendet werden um sicherzustellen, dass ein Aggregate nie einen Application Service aufruft (Aufrufe in die andere Richtung sind hingegen in einer Onion Architecture erlaubt und normal):

MATCH   (a:Aggregate)-[:DEPENDS_ON]->(s:ApplicationService)
RETURN  a.fqn AS Aggregate, s.fqn AS ApplicationService

Mit diesem Ansatz können entsprechend auch weitere unerlaubte Abhängigkeiten zwischen Stereotypen beschrieben werden. Ebenfalls ist es möglich, geforderte Struktur-Eigenschaften der Ausprägungen von Stereotypen selbst zu beschreiben, z.B:

Dabei können sich Regeln auf andere abstützen, so dass die oben beschriebene dritte Regel die durch die zweite Regel sichergestellte Semantik eines Value Objects voraussetzen kann und somit die Zusicherung erhält, dass alle vom Aggregate nach aussen gegebenen Value Objects die Anforderung an Unveränderbarkeit erfüllen.

Fazit

Dank der Abbildung von Stereotypen über Annotationen im Code und der Unterstützung durch höherwertige Konzepte von jQAssistant lassen sich Architekturregeln prägnant in der gemeinsam verstandenen Architektursprache definieren und automatisiert überprüfen. Dabei können diese Regeln direkt innerhalb der Architekturdokumentation beschrieben und begründet werden.

Durch die Integration in den Build-Prozess können Verletzungen dieser Architekturregeln bereits während der Entwicklung aufgedeckt und somit aufwändige Änderungen im Review-Prozess vermieden werden.

TAGS