Softwarearchitekturen einfacher designen und verständlicher dokumentieren mit dem Fraunhofer ADF

Architektursichten helfen, komplexe Softwaresysteme trotzdem verständlich zu dokumentieren. Das Architecture Decomposition Framework (ADF) ist ein Sichtenframework, das die zentralen Architekturaspekte durch sprechende Benennung der Sichttypen expliziter macht. Das ADF ist für alle Softwaresysteme geeignet und ist für jeden frei nutzbar. Das ADF macht das Design, die Dokumentation und das Verständnis von Architekturen bedeutend einfacher.

Architektursichten | ADF | Nutzung des ADF | Das ADF kann jeder einsetzen | Beispiele

Sichten für die Architekturdokumentation

Systeme sind komplex. Sie sind so komplex, dass es uns nicht gelingt, alle wichtigen Aspekte gleichzeitig zu erfassen, weder gedanklich während wir ein System entwerfen, noch auf Papier, wenn wir es dokumentieren wollen. Trotzdem sehen wir in unseren Projekten immer wieder die »Architekturtapete«: den (vergeblichen) Versuch, die Architektur eines Systems prägnant und trotzdem umfassend in nur einer Darstellung zu beschreiben. Das Ergebnis sind überladene Diagramme auf DIN A0, mit Dutzenden von Elementen, aber ohne Struktur und klar erkennbare Aussage. Diese Architekturdokumentation hilft allen Stakeholdern gleich wenig.

Um dem entgegenzuwirken, machen sich Softwarearchitekten oft ein Konzept zunutze, mit dem Gebäudearchitekten ganz selbstverständlich schon seit jeher arbeiten: Sichten. Kein Gebäudearchitekt käme auf die Idee, eine einzelne PowerPoint-Folie als »die Architektur des Wolkenkratzers« zu präsentieren. Stattdessen erstellen sie für jedes einzelne Gebäude eine ganze Menge verschiedener Pläne und Zeichnungen, in denen sie separat die Statik, Raumaufteilung, Elektrik, Wasserinstallation usw. beschreiben. Das lässt sich auf Software übertragen. Dem liegt die Idee zugrunde, beim Design und bei der Dokumentation von Architekturen die Komplexität zu reduzieren, indem wir nicht eine, sondern mehrere Darstellungen konstruieren, die sich jeweils nur mit einem oder wenigen Teilaspekten des Systems beschäftigen, dafür aber verständliche Strukturen besitzen und klare Kernaussagen transportieren.

Keine Darstellung kann alleine die Architektur vollständig beschreiben. Wir brauchen verschiedene Sichten.
Bilder unterliegen dem Copyright des Urhebers „Lempkesfabriek“ unter der Lizenz https://creativecommons.org/licenses/by-sa/3.0/, veröffentlicht unter https://nl.wikipedia.org/wiki/Swing_(kunstwerk)

Auch für Software ist die Verwendung von Sichten keine neue Idee. Schon Perry und Wolf haben Sichten angedacht, als sie mit ihrem Paper »Foundations for the Study of Software Architecture« den Grundstein für die Disziplin legten. Und Phillipe Kruchten definierte mit dem 4+1 Viewset bereits 1995 das erste Sichtenframework. In allen modernen Architektur-(Dokumentations-)Ansätzen wie beispielsweise dem populären arc42, spielen Sichten eine zentrale Rolle.

Das Architecture Decomposition Framework (ADF)

In vielen Projekten und Systemen haben wir die Erfahrung gemacht, dass Namen wie »Conceptual View« oder »Logical View« nicht zur Verständlichkeit und einfachen Erlernbarkeit beitragen. Deshalb haben wir aufbauend auf den existierenden Sichtenframeworks unser Architecture Decomposition Framework (ADF) entwickelt. Es hilft uns durch eine präzisere Unterscheidung und Benennung der verschiedenen Dimensionen, die unterschiedlichen Aspekte des Softwaresystems besser herauszuarbeiten und unterstützt dabei Softwarearchitekten jeden Erfahrungsgrads. Das ADF ist dabei trotzdem einfach auf alle existierenden Sichtenframeworks abbildbar.

Das ADF bildet für uns eine der wichtigsten Grundlagen für unsere Architekturarbeit und insbesondere für die Architekturdokumentation, und wir entwickeln es basierend auf den Erfahrungen in Projekten mit unseren Kunden kontinuierlich weiter. In diesem Beitrag wollen wir einen Überblick über das ADF geben, damit ihr es, oder die zugrundeliegenden Ideen, bei eurer eigenen Arbeit verwenden könnt.

Wie der Name schon vermuten lässt (Decomposition = Zerlegung), zerlegen wir mit dem ADF die Architektur eines Systems, beginnend mit einer Gesamtsicht auf das System als Black Box und kontinuierlich verfeinernd bis in die nötige Detailtiefe und in allen relevanten Aspekten. Dafür bedienen wir uns drei unterschiedlicher Dimensionen, die wir nun sukzessive einführen wollen, bevor wir erklären, wie wir das ADF beim Design und bei der Dokumentation tatsächlich einsetzen.

System-Scope

Ein System ist immer eingebettet in einen Kontext, und diesen Kontext müssen wir beim Design berücksichtigen. Da sind z.B. einerseits die Nutzer, die das System für ihre Zwecke verwenden, und andererseits andere Systeme, die mit unserem interagieren. Dies ist die erste Unterscheidung, die wir treffen. Das System von seinem Kontext abzugrenzen ist häufig nicht so offensichtlich: Gehört nun die Onboard Unit eines Fahrzeugs noch zu unserem System oder zum Kontext? Wie sieht es mit dem Mainframe der Versicherung aus, wenn wir eine neue Clientarchitektur entwickeln?

Eine einfache Faustregel, die hier hilft, ist die Frage, ob man den Teil, um den es geht, unter Kontrolle hat, also gestalten kann und muss. So kann also die native App auf dem Smartphone durchaus Teil unseres Systems sein, wenn wir sie selbst entwickeln, oder Teil des Kontexts, wenn wir unser System nur damit integrieren müssen.

System-Zeitpunkte

Eine ganz grundlegende Unterscheidung, die aber in der Praxis häufig nicht so genau gemacht wird, ist die zwischen der Laufzeit (Runtime) und der Entwicklungszeit (Development Time) eines Systems. Das ist wichtig, weil wir sonst mit Darstellungen enden, die beides unbewusst vermischen und weil uns dadurch zentrale Aspekte entgehen (was in der Praxis oft zu Mehrdeutigkeit, Missverständnissen und Fehlern führt). Zur Veranschaulichung: wenn wir einen Service mehrfach instanziieren, müssen wir in einer Laufzeitsicht mehrere Boxen zeichnen, in einer Entwicklungszeitsicht allerdings nur eine, weil es ja die gleiche Codebasis ist. Visualisieren wir den Service nur einmal, entgeht uns vielleicht die Interaktion der Serviceinstanzen untereinander, die wir eigentlich ebenfalls bedenken und designen müssen.

Aber was bedeutet diese Unterscheidung eigentlich genau? Bei Laufzeitsichten beschreiben wir das System, wie es sich während der Ausführung verhält (System in Aktion). Als Elemente verwenden wir hier vornehmlich Komponenten, Layer, Cluster/Verticals, Daten, Interfaces oder Connectors, also logische Elemente, die ein laufendes System strukturieren und die Interaktion beschreiben. Beim Design fangen wir meist auch mit solchen Sichten an, weil man sich eigentlich immer zuerst überlegt, wie das System funktionieren soll, denn dafür wird es ja gebaut. Im Gegensatz dazu überlegen wir uns bei Entwicklungszeitsichten, wie wir das System realisieren wollen (System in Entwicklung, Entwicklungsteam in Aktion). Hier arbeiten wir vornehmlich mit Modulen, Packages, Datentypen, Interfaces oder Libraries, also logischen Elementen, die entwickelt und deren Code und Abhängigkeiten organisiert werden müssen.

In unseren Seminaren stellen die Teilnehmer häufig die Frage, ob sich das eigentlich so genau unterscheiden lässt. Die Antwortet lautet: ja, überwiegend schon. Natürlich gibt es in den Sichten auch Überlappungen und Aspekte aus beiden Zeiten. Aber trotzdem sollten wir in der Lage sein, jeden Aspekt sauber zuzuordnen. Schwierig zu unterscheiden wird es eigentlich nur dann, wenn es ein Laufzeitelement gibt, das unverändert durch genau ein Entwicklungszeitelement realisiert wird. Das ist aber häufig nicht der Fall. Entwicklungszeitelemente werden zur Laufzeit oft mehrfach instanziiert, man denke im Kleinen beispielsweise an die zahlreichen Artikelobjekte einer Artikelklasse oder im Großen an die redundante Instanziierung ganzer Microservices, beispielsweise zur Erreichung eines hohen Durchsatzes.

Andererseits, werden Laufzeitelemente häufig nicht nur durch eine Klasse oder ein Modul realisiert, sondern man versucht beim Design der Entwicklungszeitstruktur, Wiederverwendung und Modularität zu optimieren. Wenn ich also bei der Realisierung meiner Event-Prozessoren immer wieder den gleichen Code schreiben müsste, um die Kommunikation mit dem Event Bus zu implementieren, hilft mir die Auslagerung der Gemeinsamkeiten in ein Modul, das ich nur in den Event-Prozessoren einbinden muss.

In der Praxis führt uns das häufig zu einer m:n-Beziehung zwischen Laufzeit- und Entwicklungszeitentitäten, also eine Komponente, die durch mehrere Module realisiert wird, die aber auch zur Laufzeit mehrfach instanziiert werden kann. Wie sich diese Beziehung im Einzelnen ausgestaltet, ist spezifisch für das jeweilige System und hängt von Faktoren wie beispielsweise dem gewählten Programmierparadigma und dem Deployment ab.

Die Brücke zwischen beiden Welten Laufzeit und Entwicklungszeit – bildet das Deployment. Normalerweise überlegen wir uns beim Design zuerst, wie das System zur Laufzeit funktionieren soll, und danach, wie wir das System im Code strukturieren wollen. Diesen Code müssen wir dann zur Ausführung bringen, indem wir ihn in Deploymentartefakte wie jars, wars, Container oder Ähnliches verpacken und dann auf Ausführungsumgebungen und/oder Maschinen deployen.

Die Unterscheidung zwischen Laufzeit und Entwicklungszeit ist also unsere zweite Dimension, die wir mit der ersten, »System / Kontext« integrieren, wie in folgender Darstellung gezeigt. Wie man sieht, berücksichtigen wir nicht nur den Kontext zur Laufzeit, wie beispielsweise andere Systeme, sondern auch zur Entwicklungszeit, mit Bezug auf beispielsweise Technologien, Entwicklungswerkzeuge oder aber auch Konzernprozesse, die wir nicht alle unbedingt unter unserer eigenen Kontrolle haben.

System-Aspekte

Zuletzt legen wir unterschiedliche Aspekte von Systemen quer über die bisherigen Dimensionen des ADF: Daten, Funktionen, Deployment, Aktivitäten und Technologien. Dies komplettiert unser Framework, wie die folgende Darstellung zeigt:

  • Beim Aspekt Daten befassen wir uns mit der Struktur und dem Fluss von Daten (Laufzeit), wie auch mit deren unterschiedlichen Typen und Formaten (Entwicklungszeit).
  • Beim Aspekt Funktionen befassen wir uns mit der Strukturierung des Systems im Sinne von funktionalen Einheiten, der Zuordnung von Verantwortlichkeiten und der Interaktion zwischen diesen Einheiten. Hier arbeiten wir mit Systemelementen, Komponenten, Layern und Clustern / Verticals zur Laufzeit sowie mit Modulen, Packages und Libraries zur Entwicklungszeit.
  • Beim Deployment-Aspekt befassen wir uns mit dem Verpacken des Codes in Deployment-Artefakte und deren Verteilung auf Rechenressourcen und Ausführungsumgebungen.
  • Unter Aktivitäten befassen wir uns eher mit einem Randbereich der Architektur, den wir aber nicht außer Acht lassen dürfen. Hier geht es um Prozesse, also Tätigkeiten von Personen, zum Betrieb (Laufzeit) und bei der Entwicklung (Entwicklungszeit). Diese Prozesse müssen wir kennen um das System für eine effiziente Entwicklung und Betrieb vorbereiten zu können.
  • Der Aspekt Technologien ist etwas speziell und steht daher auch im Bild leicht abseits. Unter dem Begriff »Technologien« verstehen wir alle Frameworks, Tools, Libraries, etc., die wir nicht selbst entwickelt haben, sondern einfach in unserem Projekt einsetzen. Technologien sind deshalb speziell, weil sie in allen anderen Aspekten vorkommen können. Wir annotieren also häufig Elemente aus den anderen Aspekten zusätzlich mit einer Technologie, wenn wir eine Technologieentscheidung zur Umsetzung getroffen haben.

Dies vervollständigt unser ADF. Die folgende Abbildung zeigt das Framework mit einer Auswahl der wichtigsten Elemente innerhalb der einzelnen Sichttypen. Welche Notation für diese gewählt wird, ist zunächst offen. In der Praxis bietet es sich aber an, eine auf der UML aufbauende Notation zu wählen, weil diese vielen Software-Ingenieuren bekannt ist und für alle Elemente des ADF geeignete Notationselemente bietet.

Die Sichttypen für Software- und Systemarchitektur

Aus der Kombination der drei Dimensionen erhalten wir 16 verschiedene Sichttypen (die Kästchen) plus die Technologien. Die Nomenklatur dabei ist einfach: Wir benennen die Sichttypen im Format <Architektur-Aspekt>@<Architektur-Zeitpunkt>, also beispielsweise Functions@Runtime oder Activities@Devtime. Der Einfachheit halber lassen wir den Scope weg, benennen also den Typ nicht noch zusätzlich mit »System« oder »Context«, einfach weil sich die meisten Sichten, die wir im Laufe eines Projektes erstellen, auf das System beziehen. Sichttypen sind auch beliebig kombinierbar, wenn wir verschiedene Aspekte gemeinsam in einer Darstellung zeigen wollen. Beispielsweise zeigen wir häufig den Datenaustausch zwischen funktionalen Einheiten und machen dafür eine Functions&Data@Runtime Sicht, oder verbinden beim Deployment  Runtime-  und Devtime- Aspekte in einer Deployment@Runtime&Devtime Sicht.

Entsprechend der grundlegenden Idee von Sichten, behandeln die unterschiedlichen Sichttypen, jeweils andere Fragestellungen:

  • Daten
    • Beispiele zur Laufzeit: »Welche Daten werden zwischen Systemen und Einheiten ausgetauscht?«, »Wo werden Daten im System erstellt, transportiert, verarbeitet und persistiert?«, »Mit welcher Frequenz werden Daten transferiert und welche Volumina von Daten werden transferiert?«
    • Beispiele zur Entwicklungszeit: «Was sind die wichtigsten Datentypen und wie stehen diese zueinander in Beziehung?«, »Welche Datenformate verwenden wir?«, »Wie persistieren wir Daten und wie gehen wir mit Object-Relational-Mapping um?«
  • Funktionen
    • Beispiele zur Laufzeit: »Mit welchen anderen Systemen interagiert unser System?«, »Wer sind die Nutzer des Systems?«, »Wie zerlegen wir das System in Laufzeiteinheiten, wie intergieren diese miteinander und welche Schnittstellen bieten sie an?«
    • Beispiele zur Entwicklungszeit: »Wie strukturieren wir den Code im Sinne von Projekten, Modulen und Packages?«, »Wie können wir durch einen geschickten Modulschnitt Coderedundanzen vermeiden und Wiederverwendung fördern?«, »Wie können Module sinnvoll entkoppelt werden?«
  • Deployment
    • Beispiele zur Laufzeit: »Welche Ausführungsumgebungen nutzen wir?«, »Welche Maschinen haben wir zur Verfügung?«, »Nutzen wir eine Cloud PaaS?«
    • Beispiele zur Entwicklungszeit: »Wie verpacken wir unseren Code in Deployment-Artefakte?«, »Wie sieht unsere Continuous Integration Pipeline aus?«, »Was für ein Versionierungsschema wollen wir nutzen?«
  • Aktivitäten
    • Beispiele zur Laufzeit: »Wie sehen Updateprozesse aus und wie wirken sie auf Laufzeitelemente?«, »Was muss für Rollouts nach Production getan werden?«,
    • Beispiele zur Entwicklungszeit: »Welches Team ist zuständig für welchen Service?«, »Welches Feature wird zu welchem Meilenstein releast?«
  • Technologien
    • Beispiele zur Laufzeit: »Welchen Cloud-Service-Anbieter verwenden wir?«, »Wollen wir einen externen Identity-Provider einsetzen?«, »Welches Persistenzframework verwenden wir?«
    • Beispiele zur Entwicklungszeit: »Welche IDE wollen wir verwenden?«, »Welche Tools verwenden wir zum Bauen und Ausliefern unseres Codes?«, »Nutzen wir Swagger für die API-Dokumentation?«

Zur Bearbeitung dieser Fragen schlägt das ADF eine Basismenge von Elementen vor, die wir in diesen Sichten jeweils typischerweise verwenden und die auch oft ausreichend sind. Darauf sind wir aber nicht festgelegt. Wir nutzen alle Elemente, die wir zur Beschreibung eines Sachverhaltes benötigen, auch wenn wir uns dafür neue einfallen lassen müssen. Wenn ich also beispielsweise Verarbeitungszyklen in eingebetten Systemen explizit als Elementtyp verwenden will, tue ich das einfach. Und dies soll uns auch als grundsätzliche Regel für die Verwendung des ADF dienen:

Die Kernaussagen über ein System, die wir transportieren wollen, stehen immer im Mittelpunkt. Das ADF ist eine Hilfestellung, die unterschiedlichen Aspekte zu berücksichtigen und sauber zu unterscheiden, soll uns aber nie einschränken.

Nutzung des ADF

Wie nutzen wir nun das ADF mit seinen unterschiedlichen Sichttypen für Softwarearchitektur im Design und der Dokumentation von Softwarearchitektur? Das ADF ist nicht so gedacht, dass wir nun genau eine Functions@Runtime-Sicht, eine Functions@Devtime-Sicht, eine Data@Runtime-Sicht usw. machen und dann sind wir fertig. Auch wenn das schön und einfacher wäre, wird das der Vielfalt und Komplexität von moderner Software nicht gerecht.

Stattdessen instanziieren wir die Sichttypen zur Beschreibung, einerseits bei der Dekomposition des Systems als Ganzes, andererseits aber auch bei der jedes einzelnen Konzeptes, das wir zur Erfüllung von Qualitätsanforderungen entwickeln. Wenn wir also beispielsweise ein Skalierbarkeitskonzept entwickelt haben, könnten wir eine Sicht erstellen, die die mehrfache Instanziierung der Services samt Loadbalancer (Functions@Runtime), die Verteilung der Instanzen auf die Knoten des Clusters (Deployment@Runtime) sowie die Datenpersistenz und -synchronisation zwischen den Instanzen (Data@Runtime) zeigt.

Bleibt die Frage: »Welche Sichten muss ich denn nun erstellen, wenn ich mein System beschreiben will?« Eine gute Frage, die im Allgemeinen aber nur schwer zu beantworten ist. Systeme sind sehr unterschiedlich, haben verschiedene Anforderungen und Komplexität und benötigen daher auch andere Konzepte, die man dann unter Verwendung von Sichten beschreiben kann. Hier nochmal die goldene Regel: Die Aussage steht im Mittelpunkt. Daher müssen wir uns überlegen, welche Message wir transportieren wollen und die können wir dann unter Verwendung des ADF beschreiben.

Trotzdem gibt es als Best Practice eine Reihe von Sichten, die bei der Initialzerlegung des Systems typischerweise erstellt werden:

  • Wenn wir ein System designen oder beschreiben wollen, erstellen wir immer zuerst eine System-Kontext-Abgrenzungssicht vom Typ Functions@Runtime. Hier zeigen wir unser System als Black Box zusammen mit Nutzern und interagierenden Systeme im Kontext.
  • Eine Detaillierungsstufe tiefer zeigen wir als nächstes die Hauptzerlegung des Systems, also die leitende Systemstruktur, als interagierende Komponenten zusammen mit ausgetauschten Daten. Die Sicht ist typischerweise vom Typ Functions&Data@Runtime.
  • Darüber hinaus zeigen wir die wichtigsten Entitäten und die Relationen zwischen ihnen, im Sinne einer Ubiquitous Language Datenmodells nach Domain-Driven Design (DDD), als Sicht vom Typ Data@Runtime.
  • Um eine Idee davon zu erhalten, wie wir das System realisieren wollen, zeigen wir auch mindestens eine grobgranulare Organisation des Source Codes unseres Systems als Sicht vom Typ Functions@Devtime.
  • Außerdem benötigt man meist auch mindestens eine Verteilungssicht, die die Zuordnung der Systemeinheiten auf Rechenressourcen zeigt. Dies ist eine Sicht vom Typ Deployment@Runtime.
  • Des Weiteren zeigen wir auch noch mindestens eine Übersicht der wichtigsten eingesetzten Laufzeit-Technologien, als Sicht vom Typ Functions&Technologies@Runtime.

Mit diesem Minimalset von Sichten hat man bei der Initialzerlegung und -beschreibung eines Systems schon eine solide Grundlage geschaffen, die eine gute Übersicht vermittelt und auf deren Basis die Architektur des System weiterentwickelt und -dokumentiert werden kann.

Darüber hinaus gibt es noch einige weitere Architekturarbeiten, bei denen das ADF gute Unterstützung leistet:

  • Bei der Redokumentation der Architektur von Systemen
  • Bei der Bewertung von Architekturen: Das ADF kann dabei helfen, systematisch alle relevanten Sichten durchzugehen und zu prüfen, ob die Architekturkonzepte die nötigen Entscheidungen enthalten
  • Beim Verstehen und Erklären von Systemen: Das ADF kann als mentales Modell dienen, um verschiedene Systemaspekte schnell zu verstehen und klar zu strukturieren

Jeder kann das ADF einsetzen

Wir verwenden das ADF bereits seit vielen Jahren in unseren Projekten, explizit für die Dokumentation; wir haben es aber auch immer im Hinterkopf, wenn wir mit unseren Kunden neue Architekturkonzepte entwickeln, um strukturiert über die verschiedenen Aspekte nachzudenken und dabei nichts Wichtiges zu vergessen. Und da es als Konzept offen zur Verfügung steht, könnt Ihr es problemlos in eurer eigenen Architekturarbeit verwenden. Dazu sind hier nochmal die wichtigsten Punkte in der Übersicht:

  • Das ADF ist ein zentraler Teil unser Architekturmethode ACES (Architecture-Centric Engineering Solutions) und die Basis für Architekturdokumentation.
  • Das ADF ist offen und kann von jedem eingesetzt werden.
  • Um das ADF in euren Projekten nutzen zu können und immer präsent zu haben, könnt Ihr das ADF als Poster herunterladen.
  • Das ADF kann für jegliche Art von Softwaresystem eingesetzt werden (Informationssysteme, eingebettete Systeme, Cloud-basierte Systeme, Software-Ökosysteme u.v.a. …).
  • Das ADF lässt sich sehr gut mit Frameworks zum Enterprise Architecture Management (EAM) integrieren.
  • Das ADF dient als Grundlage sowohl für Skizzen am Whiteboard als auch bei Modellierung mit Modellierungstools.
  • Um das ADF einfach auch im verbreiteten Tool Enterprise Architect verwenden zu können, haben wir eine EA-Erweiterung entwickelt, die Ihr zur freien Nutzung herunterladen könnt.
  • Das ADF kann auch als mentales Modell und zur Unterstützung eingesetzt werden, wenn Ihr schon mit einem anderen Sichtenframework arbeitet.
  • Tiefergehende Informationen und Übungen zu Sichten, zum ADF, zum Architekturdesign und vielen anderen Themen bieten wir in unserem Architekturseminar.
  • Darüber hinaus bieten wir auch Coaching und Unterstützung an, um beim Architekturdesign und bei der Architekturdokumentation zu unterstützen, natürlich basierend auf den Ideen des ADF.

Beispiele

Wir geben noch einige Beispiele von Sichten, die wir in unserem Projekt »Digitale Dörfer« für die dort entwickelte Plattform erstellt haben. Die Sichten folgen dem ADF und den oben beschriebenen Best Practices und sind entsprechend benannt. Sie sollen nur zur Illustration verschiedener Sichten dienen und sind natürlich keine vollständige Architekturdokumentation.