Paradigmen und Stile in der Software-Architektur
Software-Architekturen werden durch eine Vielzahl von Faktoren beeinflusst. An erster Stelle sind hier die konkreten Features, die nichtfunktionalen Qualitäten sowie die Ablaufumgebung eines Systems zu nennen. Aber auch geschäftliche und organisatorische Aspekte wirken sich ganz explizit auf die Gestaltung von Software-Architekturen aus [1], [2]. Es gibt jedoch auch gewichtige Einflussfaktoren auf Software-Architekturen, die komplett unabhängig von deren individuellen Anforderungen oder geschäftlichen und organisatorischen Aspekten sind. Zum Beispiel folgen industrielle Leitsysteme in der Regel dem gleichen architektonischen Ansatz, unabhängig von ihrem konkreten Funktionsumfang und der industriellen Domäne, die sie steuern. Und dieser Ansatz ist fundamental anders als der für IT-Systeme auf Unternehmensebene, die ebenfalls sehr oft auf einem gemeinsamen Architekturansatz aufsetzen.
Die große Mehrheit der Architekten wählt für "ihre" Systeme den richtigen architektonischen Ansatz – jedoch meist auf der Basis ihrer Erfahrung und weniger als Ergebnis einer genauen Analyse. Als pragmatische Architekten schätzen wir praktische Erfahrung, aber genauso schätzen wir konkrete Leitplanken und Methoden, um die richtigen architektonischen Entscheidungen zu treffen. Dies gilt umso mehr für fundamentale Architekturentscheidungen, die bekanntlich nur sehr schwer rückgängig zu machen sind. In diesem Artikel stelle ich zwei Konzepte vor, die helfen, sehr früh im Entwicklungsprozess die richtige Basisarchitektur für eine Software zu wählen: Problem Frames [3], [4] und Domain Driven Design [5].
Problem Frames
Problem Frames [3][4] zielen darauf ab, unabhängig von konkreten Analyse- und Entwurfsmethoden die grundsätzlichen Eigenschaften ganzer Problem- oder Aufgabenkategorien zu identifizieren, ohne dabei durch lösungsspezifische Aspekte abgelenkt zu werden, um auf dieser Basis grundlegende Eigenschaften für deren Architekturen abzuleiten und zu bestimmen.
Ein weitbekannter Problem Frame ist Simple Workpieces. Hier die Zusammenfassung: "Es wird ein Werkzeug benötigt, welches Anwendern erlaubt, Instanzen einer Klasse von formal definierten Strukturen zu erzeugen und zu editieren, sodass diese nachfolgend weiter automatisiert verarbeitet werden können, beispielsweise Texte, Grafiken und Modelle. Aufgabe ist, eine Maschine zu erstellen, die als dieses Werkzeug fungiert" [4]. Eine Reihe wohlbekannter Anwendungssysteme folgt dem Simple Workpiece-Problem Frame: UML Tools, IDEs, Grafische Editoren, CAD/CAM Systeme, usw.
Ein anderer Problem Frame ist Required Behavior: "Ein Ausschnitt der physikalischen Welt ist so zu steuern oder zu regeln, dass dieser bestimmte, definierte Bedingungen erfüllt. Aufgabe ist, eine Maschine zu bauen, welche diese Steuerung oder Regelung vornimmt" [4]. Anwendungsgebiete, die diesem Problem Frame folgen sind z. B. Systeme für die Steuerung industrieller Produktions- und Fertigungsprozesse, die Gebäudeautomatisierung oder allgemein technische Leitsysteme. Drei weitere Problem Frames sind Command Behavior, Information Display und Transformation [4]. Jeder der fünf Problem Frames ist eine klare und genaue Beschreibung einer Kategorie von Aufgaben sowie der Faktoren, welche deren Lösung fundamental beeinflussen, ohne jedoch eine konkrete Lösung vorzugeben. Problem Frames sind sozusagen Anforderungs-Patterns für eine Reihe immer wiederkehrender Problemstellungen.
Fünf Problem Frames: Simple Workpieces, Required Behavior, Command Behavior, Information Display und Transformation.
Dennoch gibt es eine sehr ausgeprägte Beziehung zwischen Problem Frames und ihren möglichen Lösungsräumen: "Ein Problem Frame ist die Verallgemeinerung eines konkreten, spezifischen Problems. Somit ist es wahrscheinlich, dass eine Methode zur Lösung dieses spezifischen Problems oder eine Lösungsarchitektur dafür auch für andere konkrete Problemstellungen trägt, die unter den Problem Frame fallen"[3].
Durch genau diese enge Beziehung zwischen ihrem Problem- und Lösungsraum bieten Problem Frames ganz konkrete Leitplanken für die Gestaltung von Architekturen an, unabhängig von deren speziellen Anforderungen. Beispielsweise sind Architekturen von Systemen, die dem Problem Frame Required Behavior folgen, typischerweise auf der Basis des Architekturmusters Shared Repository[6] gestaltet, welches mit einem Gateway von Protokoll-Adaptern[6][7] in die physikalische Welt verbunden ist, die es zu steuern oder regeln gilt. Das Repository verwaltet ein digitales Abbild der physikalischen Welt, welche durch entsprechende Domain Objects [6] und deren Zustand dargestellt wird. Zustände in dem Abbild werden durch Wertänderungen in der physikalischen Welt ausgelöst, welche über die Protokoll-Adapter im Gateway empfangen werden. Diese Zustandsänderungen lösen wiederum Systemfunktionalität aus, die sich per Publish-Subscribe [6] auf die passenden Domänenobjekte verschaltet hat. Falls Systemfunktionen ebenfalls Zustände im digitalen Abbild verändern, werden diese als Steuerungsinformation über das Gateway an die physikalische Welt weitergeleitet.
Auch die anderen vier Problem Frames sind mit einem bevorzugten Architekturstil verknüpft[6][8]. Der Simple Workpiece-Problem Frame z. B. favorisiert Architekturen, welche der Tools and Materials-Metapher folgen; der Transformation-Problem Frame legt eine Pipes & Filters-Architektur nahe. Die Problem Frames Commanded Behavior und Information Display führen zu Command Processor- bzw. Observer-Strukturen. In der Realität folgen konkrete Systeme in der Regel mehr als einem Problem Frame; ihre Architekturen sind im Kern daher auch Kombinationen der entsprechenden Architekturstile. So resultiert ein System, welches den beiden Problem Frames Commanded Behavior und Information Display folgt, auf natürliche Weise in einer Model-View-Controller, Model-View-Presenter oder Model-View-ViewModel-Architektur.
Die Kenntnis darüber, welche Problem Frames für ein Software-System relevant sind, führt somit direkt zu denjenigen Paradigmen und Stilen, die ihren Architekturen typischerweise zu Grunde liegen. Als Architekten können wir diese Paradigmen und Stile daher nutzen, um die konkreten Architekturen unserer Systeme zu gestalten. Und auch, um zu begründen, warum unsere Systeme einer ganz bestimmten Basisarchitektur folgen.
Domain-Driven Design
Im Vorwort des Buchs Domain-Driven Design [5] von Eric Evan’s merkt Martin Fowler an, "[…] dass viele Dinge eine Software-Entwicklung komplex machen. Aber: Kern dieser Entwicklungskomplexität ist immer die inhärente, natürliche Komplexität der eigentlichen Fachlichkeit des Systems. […] Der Schlüssel dafür, Komplexität in der Software-Entwicklung adäquat zu steuern, ist daher immer ein gutes fachliches Modell."
Im Zusammenhang mit der Erstellung eines fachlichen Modells gibt es eine Vielzahl von Aspekten zu beachten. Zwei Kernpunkte haben jedoch besondere Bedeutung für die Gestaltung der Software-Architektur eines Systems und die Auswahl der Technologien für deren Realisierung: die fundamentalen Eigenschaften der fachlichen Objekte sowie die Kenntnis darüber, welche Arbeitsabläufe innerhalb der Anwendungsdomäne potenziell parallel oder verteilt ablaufen können. Der Einfluss dieser beiden Faktoren auf Software-Architekturen ist orthogonal zu dem der Problem Frames – sie wirken mehr auf die Ausgestaltung ihrer Komponenten und die Interaktionen zwischen ihnen, weniger auf die Basisstruktur selbst. So hängt zum Beispiel das konkrete Design eines Shared Repository in einem industriellen Steuerungssystem von verschiedenen Faktoren ab: sind Durchsatz und Latenz die führenden Anforderungen bzgl. Performanz oder ist die Anwendungslogik zyklisch innerhalb fest vorgegebener Zeitintervalle auszuführen? Sollte das Prozessabbild der realen Welt im Hauptspeicher verwaltet werden oder ist es besser in eine Datenbank auszulagern?
Hinweise, wie solcher Art Entwurfsentscheidungen zu treffen sind, ergeben sich aus den fundamentalen Eigenschaften des fachlichen Modells für eine Anwendung. Bei fachlichen Objekten lassen sich z. B. zwei grundsätzliche Kategorien unterscheiden: Objekte, die physikalische Dinge der realen Welt repräsentieren, zum Beispiel ein Antrieb oder ein Kessel, und Objekte, die logische Dinge darstellen, beispielsweise ein Arbeitsplan. Physikalische Dinge unterliegen in vielen Anwendungsdomänen sehr stringenten Randbedingungen. Sei es, dass sie hochverfügbar sein müssen, ein Realzeitverhalten haben, oder dass Aktionen, die auf ihnen ausgeführt werden, irreversibel sind. Logische Dinge hingegen haben in der Regel ein weiches (Real-)Zeitverhalten, und Aktionen, die auf ihnen ausgeführt werden, können storniert oder gar rückgängig gemacht werden.
Übertragen auf unser Beispiel eines technischen Leitsystems ergibt sich so die Erkenntnis, dass die fachlichen Objekte den aktuellen Zustand des physikalischen Prozesses, den es zu steuern oder regeln gilt, genau und konsistent abbilden müssen. Verhalten nahe der Realzeit und eine hohe Verfügbarkeit ist essenziell, denn ein physikalischer Prozess "hält nicht an", wenn das Leitsystem etwas länger braucht, um eine Aktion auszuführen, oder gerade nicht verfügbar ist. Hoher Durchsatz ist eine weitere Anforderung – an einem physikalischen Prozess sind in der Regel sehr viele Objekte beteiligt. Die Kenntnis dieser Eigenschaften bestimmt maßgeblich die konkrete Ausgestaltung des Shared Repository-Architekturstils, unabhängig davon, welche genauen Anforderungen an das Leitsystem gestellt werden:
- Um schnelle Umlaufzeiten des Kontrollflusses vom Leitsystem in den physikalischen Prozess und zurück zu gewährleisten, ist das Shared Repository eine zustandsbehaftete Komponente, die das digitale Prozessabbild im Hauptspeicher verwaltet; Datenbanken werden "nur" für die Speicherung der Projektkonfiguration und das Archivieren historischer Prozesszustände eingesetzt.
- Funktionalitäten, die auf dem Shared Repository aufsetzen, werden als zustandslose Dienste gestaltet, um die Integrität und Konsistenz des Prozessabbildes nicht zu gefährden.
- Die Hochverfügbarkeit des gesamten Systems wird für das Shared Repository durch Active Hot-Standby-Redundanz sichergestellt, sowie durch Parallelredundanz für die funktionalen Dienste und die Bedienoberfläche.
Haben die fachlichen Objekte des Software-Systems andere Eigenschaften, so ergeben sich auch andere Entwurfsentscheidungen für das Detaildesign von dessen Architektur. Zum Beispiel werden Objekte, die in ein deterministisches, zyklisch auszuführendes Realzeitverhalten aufweisen müssen, idealerweise nach den Regeln einer Time-Triggered Architecture [8] gestaltet und orchestriert. Für Anwendungen mit Objekten, die logische Dinge repräsentieren und vorwiegend dispositiv bearbeitet werden, eignet sich am besten eine "klassische" Service-orientierte Architektur, entweder realisiert als Micro-Services oder auf der Basis eines Enterprise-Service Bus [8].
Die Arbeitsabläufe und Geschäftsprozesse, die in einem fachlichen Modell dargestellt und auf den fachlichen Objekten ausgeführt werden, geben wiederum Hinweise darauf, wie die Verteilungs- und Nebenläufigkeitsarchitektur einer Software gestaltet werden kann. Ziel einer adäquaten Nutzung von Verteilung und Nebenläufigkeit ist, die zur Verfügung stehenden Rechenkapazitäten eines Systems maximal zu nutzen, dabei aber Kommunikation über das Netzwerk sowie den Bedarf an Synchronisation zwischen Threads zu minimieren. Dieses Ziel lässt sich auf einfache und direkte Weise dadurch erreichen, dass die "natürliche" Verteilung und Nebenläufigkeit von Arbeitsabläufen in einem fachlichen Modell direkt in der Software-Architektur abgebildet wird. Drei grundlegende Prinzipien sind zu beachten [8]:
- Lokalität. Daten und Funktionen werden möglichst lokal an dem physikalischen Ort verwaltet bzw. bereitgestellt, wo diese auch benötigt werden, z. B. in einem Leitsystem zur Steuerung verschiedener Anlagenbereiche.
- Unabhängigkeit. Arbeitsabläufe, die in der realen Welt parallel und verteilt ablaufen, sollten genauso parallel und verteilt in der Software ablaufen können, z. B. die Steuerung von verschiedenen Produktionslinien in einem Leitsystem.
- Arbeitsteilung. Daten, deren Verarbeitung in mehrere parallel bearbeitbare Blöcke oder ausführbare Arbeitsschritte unterteilt werden kann, sollten auch entsprechend parallel in der Software verarbeitet werden, beispielsweise bei einer Bildverarbeitung.
Alle drei Prinzipien werden unmittelbar durch Entwurfsmuster, Entwurfsmethoden und in manchen Programmiersprachen sogar durch passende Ablaufumgebungen unterstützt. Zum Beispiel:
- Eine Broker Architectures [6] unterstützt Lokalität von Daten und Funktionen.
- Die Entwurfsmuster Leader/Followers, Half-Sync/Half-Async, Active Object und Proactor [6] definieren Ansätze zur parallelen Ausführung von unabhängigen Arbeitsabläufen.
- Das Entwurfsmuster Master-Slave [6] bildet das Prinzip "Divide and Conquer" ab und erlaubt so die parallele Ausführung von Taskbäumen und die arbeitsteilige Verarbeitung von Daten.
- Parallele Pipes and Filters bzw. Layers [9] Architekturen unterstützen die parallele Verarbeitung von Datenströmen bzw. die parallele Ausführung von Anwendungsdiensten.
- Das Actor [10] Programmiermodell unterstützt die parallele Ausführung unabhängiger Arbeitsabläufe.
- Programmierplattformen mit "eingebauter" Parallelität wie Erlang [11] erlauben es, Nebenläufigkeit als explizites Programmierparadigma zu verwenden.
Zusammenfassend lässt sich feststellen, dass tragfähige Software-Architekturen die fundamentalen Eigenschaften ihrer Anwendungsdomäne explizit abbilden und so sicherstellen, dass deren konkrete Funktionalität adäquat realisiert werden kann, inklusive aller konkreten nicht-funktionalen Anforderungen. Diese Erkenntnis ist unabhängig von den konkreten Anforderungen einer Software als auch von anderen Einflussfaktoren, die auf deren Erstellung wirken, beispielsweise geschäftliche und organisatorische Aspekte. Mit Hilfe von Problem Frames und Domain-Driven Design lassen sich sehr schnell die grundlegenden Architekturstile und Paradigmen bestimmen, die einer Software zugrunde liegen, sowie fundamentale Entwurfs- und Technologieentscheidungen für die konkrete Ausgestaltung von deren Komponenten treffen. Zentrale Architekturentscheidungen, die typischerweise nur sehr kostspielig änderbar sind, werden somit auf der Basis klarer Vorgaben und Schlussfolgerungen getroffen und nicht rein auf der Basis der persönlichen Erfahrung eines Architekten oder Entwicklungsteams. Die aus solch fundierten Entscheidungen abgeleitete Basisarchitektur bildet einen stabilen Rahmen für eine agile Entwicklung von deren konkreter Funktionalität.
- J. Maranzano et al: Architecture Reviews: Practice and Experience, IEEE Software, vol. 22, no. 2, 2005, pp. 26-32.
- M.E. Conway: How Do Committees Invent?, Datamation, Apr. 1968.
- M. Jackson, Software Requirements & Specifications: A Lexicon of Practice, Principles and Prejudices, Addison-Wesley, 1995.
- M. Jackson: Problem Frames, Addison-Wesley, 2001.
- E. Evans: Domain Driven Design, Addison-Wesley, 2004.
- F. Buschmann, K. Henney and D.C. Schmidt: Pattern-Oriented Software Architecture: A Pattern Language for Distributed Computing, volume 4, John Wiley and Sons, 2007.
- E. Gamma et al.: Design Patterns: Elements of Resuable Object-Oriented Software, Addison-Wesley, 1995.
- F. Buschmann: Software Architecture Paradigms and Styles, Java and Object-Oriented Conf. (JAOO), 2009.
- J.L. Ortega-Arjona: Patterns for Parallel Software Design, John Wiley and Sons, 2010.
- G. Agha, Actors: A Model of Concurrent Computation in Distributed Systems, doctoral dissertation, MIT Press, 1986.
- J. Armstrong: Programming Erlang – Software for a Concurrent World, Pragmatic Bookshelf, 2007.