Software-Architektur-Design Die Definition wie alles ineinander wirkt

Software-Architektur-Design
Die Definition wie alles ineinander wirkt

Heute stellt sich oftmals das Architektur-Design als mächtigster Mechanismus im Software-Engineering dar, um Software-Qualitätsattribute wie Wiederverwendbarkeit, Verstehbarkeit, Wartbarkeit, Robustheit, Testbarkeit usw. zu verbessern. Architektur-Design zielt auf sehr viele Gesichtspunkte eines Systems und dessen strukturellen Aufbau hinsichtlich dieser Gesichtspunkte. Häufig wird das Architektur-Design als statische Aufteilung des Systems in logischen Komponenten gesehen. Das ist aber nur ein Bereich von vielen. Andere Bereiche sind die Darstellung und Strukturierung von Versionen, Varianten und deren Unterschiede zueinander, verschiedene Betriebsmodi oder auch die Struktur von Daten. Letztendlich gibt es noch den Blickwinkel auf das dynamische Verhalten, auch Laufzeit-Architektur genannt. Dieses ist in ablauforientierten Systemen mit hohen Zeitanforderungen die Basis für Wartbarkeit, Erweiterbarkeit, Wiederverwendbarkeit usw. Trotzdem wird diesem Teil des Architektur-Designs meist wenig Beachtung geschenkt. In diesem Artikel erfahren Sie mehr.
Aktuell im Jahr 2011 wirbt die OOP (einer der wichtigsten und größten Kongresse im Bereich Software-Engineering) speziell mit dem Thema Software-Architektur-Design. Es werden Software-Engineering-Gurus zitiert, die Architektur-Design als stark vernachlässigtes Thema halten, obwohl es als Dreh- und Angelpunkt für die wesentlichen Qualitätsmerkmale heutiger Software-Systeme gilt. Auf der Website des Software Engineering Institute (SEI) steht zu diesem Thema: „Die Architektur ist der primäre Träger der System-Qualitäten, wie Leistung, Modifizierbarkeit und Sicherheit, von denen keine ohne eine zusammenführende architektonische Vision erreicht werden kann.“ (Quelle: http://www.sei.cmu.edu/architecture/)

Was zeichnet ein gutes Architektur-Design aus?

Um diese Frage zu beantworten, müssen wir erst einmal definieren, was denn eigentlich das Ziel des Software-Architektur-Designs ist. Das ist relativ einfach, die Basisidee einer jeden Software Architektur ist die Anwendung eines seit Tausenden von Jahren erfolgreich angewandten Mechanismus: ‚divide et impera‘ (‚teile und herrsche‘), im Engineering auch als reduktionistischer Lösungsansatz bezeichnet. Wenn ein Trend im Engineering sicher ist, dann ist es das Wachstum der Komplexität. Nahezu genau so sicher ist die zunehmende Geschwindigkeit, mit der die Komplexität wächst. Es geht darum, diesem Trend entgegenzuwirken, indem ein komplexes System so lange in kleinere Teile zerlegt wird, bis deren Komplexität beherrschbar ist. Anschließend wird aus diesen Teillösungen eine Lösung für das Gesamtproblem (re-)konstruiert. Aber nicht immer scheint dieser Ansatz zum Ziel zu führen, was dann fast immer an Art und Ausprägung der Schnittstellen liegt.

Art und Auslegung der Schnittstelle

Betrachten wir den heutigen Charakter vieler Architekturen einmal auf Basis der Schnittstellen. Dort entsteht trotz der Anwendung von ‚divide et impera‘ die Komplexität. In einem System wächst die Anzahl der Beziehungen exponentiell zur Anzahl der Elemente (siehe Bild 1). Nun werden sicher einige Softwareentwickler bedenklich und behaupten: Aber nicht jedes Software-Modul (Element) hat eine Beziehung zu jedem anderen Modul. Und ich gebe Ihnen recht. Denn jedes Modul hat mehrere Beziehungen zu jedem anderen Modul. So haben Sie es nicht gemeint? Ich weiß, und werde erklären, dass es doch so ist. Betrachten wir Funktionen in einer einfachen Laufzeitarchitektur, der sogenannten main() Loop. Dort werden alle Funktionen nacheinander abgearbeitet. Die Periodizität aus zeitlicher Sicht ergibt sich für jede einzelne Zeile Code durch die Laufzeit der gesamten main()-Schleife. Wird nun in nur einer Funktion durch eine Änderung die Laufzeit erhöht, dann ändert sich die Periodizität für alle anderen Funktionen. Das kann unmerklich und ohne funktionale Änderungen geschehen, aber eventuell nach der 384. Änderung wird die minimale Reaktionszeit einer der Funktionen überschritten und das hat nun doch funktionales Fehlverhalten zur Folge. In diesem Fall sind also alle Elemente der main() Loop über der Ebene der Zeit miteinander verbunden.

Weitere Ebenen

Neben der Zeitebene haben wir im Software Engineering weitere Ebenen. Aus Sicht des Software-Engineering sind das:

Zeit

Datenfluss

Logik

Kontrollfluss

Prioritäten (nur bei preemptiven

Systemen)

Aus Sicht der Applikation kommen weitere Ebenen hinzu:

Varianzen über die Zeit (Versionen)

Varianzen zum Zeitpunkt der Produktion (Varianten)

Varianzen zum Zeitpunkt des Betriebes (Betriebsmodi)

Überlagert Kontrollflüsse (Notaus, Errorhandling usw.)

In der Praxis sind in einem Design sieben bis zwölf Ebenen zu berücksichtigen. Auf all diesen Ebenen können sich die Einheiten (wie immer wir sie bezeichnen, Module, Funktionen, Prozesse, Klassen, Objekte usw.) gegenseitig beeinflussen. Sicher bezogen auf eine Ebene hat ein Element nicht unbedingt eine Beziehung zu jedem anderen, aber über alle Ebenen hinweg betrachtet, sind es in der Regel mehrere. Nun ist jedoch ‚teile und herrsche‘ eine der effizientesten Möglichkeiten Komplexität beherrschbar zu machen. In den meisten Architekturen wird das ‚teile‘ jedoch nur sehr halbherzig implementiert. Oftmals nur auf der statischen Ebene auf dem Papier. Eine der Hauptaufgaben des Architektur-Designs ist es, die Entkopplung auf so vielen Ebenen wie möglich zu erreichen. Damit haben wir eine Qualität in Bezug auf obige Frage, was ein gutes Architektur-Design ausmacht, identifiziert. Es ist die Qualität der Schnittstellen. Wie wir schon gesehen haben, ist die main() Loop kein geeigneter Laufzeitpattern, um eine Entkopplung auf der Zeitebene zu bekommen. Kooperatives Multitasking ist besser, preemptives Multitasking noch besser. Je nach Anforderung werden Pattern ausgewählt, die das notwendige Maß an Entkopplung ermöglichen. Wir werden jedoch keine 100%-Entkopplung auf allen Ebenen erreichen, was auch gar nicht sinnvoll wäre, denn auf einer Ebene müssen Kontrollfluss und/oder Datenfluss stattfinden. Aber wir können eine Ebene definieren, auf der die notwendige Kommunikation stattfindet und dann mit geeigneten Pattern Einflüsse aus anderen Ebenen soweit wie möglich verhindern. (Encapsulation). Die Frage ist nur, auf welche Ebene konzentrieren wir uns und auf welchen anderen Ebenen sorgen wir für größtmögliche Entkopplung? Die Antwort ist abhängig vom Charakter der Applikation. Grundsätzlich kann eine Software-Applikation in zwei Charaktere unterschieden werden, daten- oder ablauforientiert. In technischen Embedded-Systemen haben wir es fast immer mit einem eher ablauforientierten Charakter zu tun. Aus diesem Grund beschränken wir uns in den folgenden Betrachtungen auf laufzeitorientierte Systeme. Die Architektur der damit verbundenen Basis-Ebene wird als Laufzeit-Architektur bezeichnet.

Wie erreichen wir eine gute Architektur?

Ein grundlegendes Qualitätsattribut einer guten Architektur ist also Encapsulation. Um diese zu erreichen, halte ich es für sehr wichtig, das Architektur-Design in zwei Gesichtspunkte zu trennen.
1. Applikations-Sicht (Was soll gebaut werden, Struktur aus Sicht der Applikation)
2. Software-Engineering-Sicht (Wie soll gebaut werden, Architekturmuster, Architekturstil, Baumaterial) Was meine ich damit? Stellen wir uns die Konstruktion und den Bau eines Regals vor. Das Regal könnte aus einem Holzstamm aus dem Vollen gefräst werden, was sicherlich eine der komplexesten und teuersten Produktionsverfahren wäre. Um das Regal effizienter herzustellen, wenden wir also ‚teile und herrsche‘ an. Wir zerlegen es in Komponenten (Regalböden, Leiste, Seitenwände, Rückwand usw.), die wesentlich einfacher gefertigt werden können und vor allem weniger Abfall produzieren. Nun müssen diese Komponenten zu einem Regal zusammengefügt werden. Dafür bieten sich verschiedene Möglichkeiten an. Z.B. können die Regalböden mit den Seitenwänden gedübelt und verleimt oder verschraubt werden. Für das Endprodukt, das zusammengebaute Regal, macht es eventuell keinen Unterschied, aber im Fall eines Transportes (Service und Wartung) ist es ein großer Unterschied, ob die Verbindung lösbar (Schraube) oder fest (Leim) gestaltet ist. Die Wahl der Verbindung hat weniger Einfluss auf das eigentliche Applikations-Design (Zusammengebaut bekommen wir nahezu das gleiche Ergebnis), sondern auf Wartbarkeit und Effizienz in der Produktion. Angenommen wir entscheiden uns für das Verschrauben, dann bieten sich auch hier wieder verschiedene Arten, Selbstscheidenden- oder Maschinen-Gewinde, Senkkopf- oder Linsenkopf-Schrauben. Auch hier haben wir wieder einen großen Einfluss auf Effizienz in der Produktion. Sicher werden wir ausschließlich Schrauben für dasselbe Werkzeug einsetzen und nicht Kreuzschlitz, Torx, Imbus oder Sechskant vermischen. Das gewährt wenig und damit kurze Rüstzeiten in der Produktion. Die reine Umsetzung der funktionalen Anforderung bezeichne ich als Applikations-Design. Die Wahl der Konstruktionsmuster und Halbzeuge als Architektur-Design. Unter gleichen Konstruktionsbedingungen (Stabilität, Auf- und Abbaumöglichkeit, Abmessungen usw.) ergibt sich ein großer Spielraum an Gestaltung der Konstruktionsmuster und Halbzeuge. Dieser Spielraum zielt im Wesentlichen auf die Effizienz in der Produktion, aber auch ganz wesentlich auf die Qualitätsattribute wie Wartbarkeit, Erweiterbarkeit. Dasselbe gilt für Software-Architekturen. Die Architektur legt fest, was wie gebaut werden soll. Auch hier beeinflusst die Wahl von Best-Practice-Ansätzen (in Form von Software-Mustern), Art und Beschaffenheit von Halbzeugen (RTOS, BSP, Eigenschaften der Hardware usw.) direkt die Produktivität in der folgenden Implementationsphase bzw. die Qualitätsattribute der Software.

Homogen, strukturiert, effizient

Architektur bedeutet im Wesentlichen, Rahmenbedingungen für die Implementierung vorzugeben. Ziel ist es, mit diesen Rahmenbedingungen dafür zu sorgen, dass sowohl die Implementierung selbst effizient durchgeführt werden kann als auch dass ein homogenes, strukturiertes, effizientes System entsteht. Und die Rahmenbedingungen sind heute nicht trivial. Eventuell arbeiten verschiedene Entwickler an verschiedenen Standorten mit unterschiedlichen Technologien. Alte, neue und zugekaufte Software-Komponenten müssen vereint werden, Varianten und Versionen entstehen oder existieren ..all das soll am Ende zu einem robusten, wartbaren, änderbaren System verschmelzen. Die Architektur legt den Grundstein dafür. „Architektur ist der Dreh- und Angelpunkt für die hochkomplexen Systeme, die wir jetzt und in Zukunft brauchen.“ (Quelle: Rolf Siegers, Raytheon)

Die Architektur ist Dreh- und Angelpunkt

Sheduling Pattern, wie main() Loop, Round Robin, kooperatives und preemptives Multitasking, aber auch die Kommunikations-Pattern, wie Signale, Ereignisse, Nachrichten mit ihren unterschiedlichen Charakteren aus zeitlicher Sicht kontinuierlich oder diskret, synchron oder asynchron. Die Pattern können nicht wahllos kombiniert werden. Genau wie eine Schraube mit selbstschneidendem Gewinde nicht gut mit einer Mutter mit metrischem Gewinde harmonisiert, harmonisieren auch Software-Pattern mehr oder weniger gut miteinander. So harmonisieren z.B. preemptive Systeme nicht besonders gut mit synchronen Daten. Wird z.B. ein Software-System mit einer kooperativen Laufzeitarchitektur in Verbindung mit synchroner Datenkommunikation implementiert und im Nachhinein entsteht die Anforderung einer preemptiven Lauzeitarchitektur, dann kann dies nur mit erheblichem Aufwand eingeführt werden, da sie wesentlich besser mit asynchronen Kommunikations-Mustern harmonisiert. In der Praxis würde man die grundlegende Architektur nicht ändern, sondern nur einzelne Tasks preemptiv gestalten, jedoch mit großen Nebenwirkungen auf der Kommunikationsebene, da diese synchron ist. Ähnlich verhält es sich häufig, wenn Komponenten aus einer kooperativen Architektur in einer preemptiven Architektur wiederverwendet werden sollen. Architektonische Kompromisse sind die Basis für sogenannte Software-Erosion. Die Architektur ist nun nicht mehr homogen, und damit empfindlich für unvorhersehbare Effekte bei weiteren Änderungen. Damit einhergehend sinken Robustheit, Änderbarkeit.

Architektur-Design: eine große Herausforderung

Die Architektur ist immer eine Gratwanderung zwischen der Beibehaltung von etablierten oder Einsatz von neuen Mustern, Wiederverwendung oder Neuentwicklung, Eigenentwicklung oder Zukauf von Komponenten. Das macht es so schwierig, denn wer weiß im Vorhinein, was günstiger sein wird, eine alte Komponente anzupassen oder diese besser neu zu entwickeln. In der Regel wird man es nicht einmal am Ende des Projektes wissen, denn es wurde sich ja für einen Weg entschieden und nur dieser durchgeführt. Der Ausgang des anderen Weges bleibt für immer Spekulation. Ob es effizienter ist, eine alte Komponente in einem neuen Projekt wieder zu verwenden oder neu zu programmieren entscheidet sich jedoch häufig über das Architektur-Design. Ist z.B. die alte Komponente in einer zeitgesteuerten Laufzeitarchitektur und Kommunikation mit zeitkontinuierlichen Daten entstanden, und soll nun in einer OO-Architektur mit zeitdiskreten Daten wiederverwendet werden, so ist mit erheblichem Aufwand an den Interfaces zu rechnen. Oft scheitern neue Designversuche daran, dass wiederverwendete Komponenten die Architektur in alte Strickmuster zwingen. Vor allem in den Köpfen der Entwickler. Hat das Projekt einen langen Lebenszyklus, dann wäre in diesem Fall sicher die Entscheidung besser gewesen, die wiederverwendeten Komponenten neu zu programmieren, zugunsten der Architektur. Der Grad der Effizienzsteigerung durch Wiederverwendung entscheidet sich auf Basis der Schnittstellen. Passen diese, ist der zu erwartende Effizienzgewinn groß, passen diese nicht, ist es umgekehrt. Zwingen alte Komponenten die neue Architektur in die alten Strukturen zurück, dann leidet die Effizienz im gesamten Projekt. Die Kunst des Architekten liegt darin, die grundlegenden Eigenschaften einer Applikation zu erkennen und dementsprechend geeignete Muster auszuwählen und deren Anwendung als Rahmensatz zu definieren. Diese liefert die Basis für eine effiziente Implementiereung und der beste Schutz gegen frühzeitige Software-Erosion. Auf dieser Basis wird dann die Systemarchitektur aufgebaut.

Willert Software Tools GmbH
www.willert.de

Das könnte Sie auch Interessieren