Blank billboards2-[Konvertiert]

Die Architektur eines Softwaresystems wird maßgeblich durch die funktionalen und nicht-funktionalen Anforderungen bestimmt. Sie soll so gestaltet sein, dass sie die vorgegebenen Qualitätsmerkmale bezüglich der Anforderungen in bestmöglicher Art erfüllen kann. Unzählige Entwickler und Architekten standen schon vor dieser anspruchsvollen Auf­gabe. Aus diesen Erfahrungen haben sich im Laufe der Zeit eine Reihe von bewährten Lösungsschemata für bestimmte Problemstellungen ergeben, die als Architekturmuster oder -stile bezeichnet werden.

Begriffe

Architekturmuster und -stile stellen geprüfte Lösungsschemata für die grundlegende Organisation von Softwaresystemen dar. Sie legen die Art und Weise fest, wie Systeme aufgeteilt werden und wie die einzelnen Teile in Beziehung stehen und miteinander kommunizieren.

Architekturmuster unterstützen dabei, die Komplexität von Softwaresystemen in den Griff zu bekommen, indem sie für eine bestimmte Problemstellung einen wiederverwendbaren Lösungsansatz zur Verfügung stellen.

Architekturmuster sind grundsätzlich plattform- und sprachenunabhängig. Ihr Fokus liegt auf dem Gesamtsystem und bildet die übergeordnete Ebene einer dem Abstraktionsgrad nach geordneten Hierarchie (siehe Abbildung 1).

afl abb1
Abb. 1: Hierarchie Architekturmuster.

In der zweiten Ebene dieser Hierarchie findet man die klassischen Design-Patterns („GoF“, [1] und [2]). Architektur­muster sind in der Regel aus einer Menge von Design-Patterns aufgebaut, die nach spezifischen Kriterien kombiniert werden und so die Charakteristik eines Architekturmusters bestimmen. Letztlich findet man in der untersten Ebene die Idiome. Sie stellen „Best Practices“ im Umgang mit der Programmiersprache dar und sind entsprechend sprachspezifisch. Sie helfen, die Design-Patterns möglichst effektiv zu implementieren.

Einteilung

Architekturmuster lassen sich in verschiedene Kategorien einteilen. Diese Einteilung orientiert sich an der Problemstellung, die das jeweilige Muster hauptsächlich adressiert. Die Kategorien sind jedoch nicht immer ganz klar zu trennen, da die jeweils betrachteten Aspekte sich in den Mustern teilweise überschneiden bzw. unterschiedliche Gewichtungen besitzen. Die folgende Aufzählung enthält die in der Praxis am häufigsten vorkommenden Problemfelder und die bekanntesten dazugehörigen Architekturmuster:

  • Strukturierung von Subsystemen & Komponenten (Strukturierungsmuster)
    • Schichtenarchitektur (geschichtete
    • Abstraktionsebenen)
    • Blackboard (kooperative Subsysteme)
    • Pipes & Filters (Verarbeitung von Datenströmen)
  • Anpassung und Veränderung von Systemen (Muster für erweiterbare Systeme)
    • Microkernel (flexible & modulare Systemerweiterung)
  • Verteilung von Komponenten über Netzwerke(Muster für verteilte Systeme)
    • Client-Server (verteilte Aufgaben im Netzwerk)
    • Broker (im Netzwerk verteilte undentkoppelte Komponenten)
  • Strukturierung der Mensch-Maschine-Kommunikation (Muster für Benutzerinteraktion)
    • Model-View-Controller (Trennung von Daten, Steuerung und Darstellung)

Die tiefe Kenntnis der verschiedenen Muster ist für den Architekten sehr wichtig. Denn nur wenn er die Vor- und Nachteile eines Musters und dessen Auswirk­ungen auf die nicht-funktionalen Anforderungen wie Wartbarkeit, Performance, Skalierbarkeit etc. einzuordnen weiß, ist er in der Lage eine optimale Architektur zu entwickeln. Was das genau bedeutet soll im Folgenden anhand zweier Beispiele erläutert werden.

Layers

Eines der am häufigsten vorkommenden Muster ist sicherlich die Schichtenarchitektur. Sie gehört zu den Strukturierungsmustern und ordnet die einzelnen Komponenten eines Systems in Schichten an, die vertikal gestapelt werden. Bausteine in gleichen Schichten sollen etwa den gleichen Abstraktionsgrad besitzen, der von den unteren zu den oberen Schichten hin zunimmt.

Jede Schicht kapselt eine bestimmte Funktionalität und bietet diese über eine definierte Schnittstelle der übergeordneten Schicht als Dienst an. In der strengen Auslegung der Schichtenarchitektur dürfen Komponenten einer Schicht nur Komponenten der eigenen oder direkt untergeordneten Schicht aufrufen (siehe Abbildung 2, Fall A). Das Überspringen von einzelnen Schichten oder die Nutzung übergeordneter Schichten ist in dieser Ausprägung verboten (siehe Abbildung 2, Fall B). In einer weniger strengen Variante des Musters wird aber auch ein Überspringen von Schichten - jeweils von oben nach unten - erlaubt (siehe Abbildung 2, Fall C).

afl abb2
Abb. 1: Schichtenarchitektur.

Die Vorteile dieses leichtverständlichen Musters liegen auf der Hand. Durch die Kapselung der Funktionali­täten werden die Abhängigkeiten sowohl zwischen den Schichten als auch zwischen den Komponenten reduziert, einzelne Schichten benötigen nur noch Kenntnis über die Schnittstelle der untergeordneten Schicht. Sofern die Schnittstelle erhalten bleibt, lassen sich damit Schichten sehr leicht austauschen.

Das klassische Beispiel der Schichtenarchitektur ist das OSI/ISO-Referenzmodell zur Kommunikation, das von den oberen Schichten mit Protokollen wie HTTP, TCP oder IP bis auf die unteren physikalischen Schichten reicht, in den u.a. Signale auf elektrischen Leitern definiert werden.

Für Standardapplikationen wird gerne ein 3- oder 4-Schichten-Ansatz gewählt. So propagiert Eric Evans in „Domain Driven Design“ [2] folgende vier Schichten:

  • Präsentationsschicht (Presentation Layer)
    Benutzeroberfläche mit Darstellung von Ausgabedaten und Eingabe von Benutzerdaten
  • Anwendungsschicht (Application Layer)
    allgemeine Geschäftsprozesse
  • Fachmodellschicht (Domain Layer)
    Datenmodell der fachlichen Entitäten
  • Infrastrukturschicht (Infrastructure Layer)
    technische Dienste wie z.B. Persistenz und Kommunikation

Die Herausforderung bei diesem Muster ist die Identifikation des passenden Abstraktionsgrads. Denn dieser bestimmt den Zuschnitt der einzelnen Schichten. Des Weiteren gilt es, die dazugehörigen Funktionalitäten zu erkennen und diese in Komponenten zu kapseln. Dabei spielt die Ausgestaltung der dazugehörigen Schnittstellen eine entscheidende Rolle, da diese auch die Schnittstellen der Schichten bilden.

Eine Schichtenarchitektur hat aber nicht nur Vorteile. Durch die Entkoppelung der Schichten wird eine Vielzahl an Schnittstellen eingeführt. Beim Transport der Daten über diese Schnittstellen, müssen diese in der Regel aufbereitet oder umstrukturiert werden. Dies erhöht den Entwicklungsaufwand und kann sich negativ auf die Performance des Gesamtsystems auswirken. Werden zukünftige Änderungsanforderungen bei der Systementwicklung nicht richtig berücksichtigt, können außerdem kleine Anpassungen zu enormen Aufwänden führen, die sich durch alle Schichten ziehen.

Das Muster gilt als Universalmittel zur Strukturierung von Standardanwendungen. Und genau hier liegt auch eine Gefahr. Architekten und Entwickler neigen dazu es häufig unreflektiert einzusetzen und vergeben damit oft die Chance, eine alternative, eventuell effektivere Lösung zu finden.

Microkernel

Das Microkernel-Muster adressiert Systeme, die Anforderungen an große Anpassbarkeit und Flexibilität genügen müssen. Die Kernkomponente (der Microkernel) kapselt zentrale Dienste, wie Kommunikations­mechanismen und Ressourcen-Management und stellt diese über eine definierte Schnittstelle anderen Komponenten zur Verfügung.

Dabei abstrahiert er von den spezifischen Eigenschaften der jeweiligen Plattform und ermöglicht damit eine erhöhte Portabilität. Die klassische Domäne für dieses Muster sind Betriebssysteme (siehe Abbildung 3). Die Aufgabe des Microkernels ist dort die Abstraktion von der Hardware. Soll das Betriebssystem später auf eine andere Hardware portiert werden, muss dazu lediglich der Mirokernel adaptiert werden.

afl abb3
Abb. 3: Typisches Muster bei Betriebssystemen.

n-Funktionalität

Zusätzliche Funktionalitäten können in Form von Subsystemen implementiert werden, die jeweils die Dienste des Microkernels nutzen. Diese Subsysteme werden vom Microkernel in der Regel dynamisch verwaltet. Wird von externen Anwendungen ein Dienst eines Subsystems angefordert, ist der Microkernel in der Regel in der Lage dieses über einen entsprechenden Mechanismus nachzuladen.

Neben Betriebssystemen findet sich dieses Muster in diversen Varianten, oft auch in Application Servern, virtuellen Maschinen, Datenbanken und allen anderen Formen von Anwendungen, in denen beispielsweise Plugin-Mechanismen implementiert werden. Die Herausforderung bei solchen Systemen liegt in der effizienten Verwaltung der Ressourcen - beispielsweise Prozesse, Speicher und Kommunikationskanäle.

Ein übliches Vorgehen ist es dabei, die Subsysteme in interne und externe Dienste zu unterteilen. Dienste, die häufig verwendet werden, genießen eine Art bevorzugte Behandlung und werden teilweise direkt vom Microkernel verarbeitet. Sie werden als interne Dienste bezeichnet. Dagegen werden externe Dienste unter Umständen über andere Mechanismen an das jeweilige Subsystem adaptiert bzw. delegiert.

Wie bereits ausgeführt, sind die eigentlichen Stärken dieses Musters im Bereich Portierbarkeit, Flexibiltät und Anpassbarkeit zu finden. Wie bei jeder Medaille gibt es allerdings auch hier die Kehrseite. Im Vergleich zu einem monolithischen Kernel, wird der Micro­kernel im Hinblick auf die Ausführungsgeschwindig­keit schlechter abschneiden. Dies ist dem zusätzlichen Kommunikationsaufwand zwischen den verschiedenen Komponenten geschuldet. Deshalb muss bei der Implementierung einer Microkernel-Architektur diesen Mechanismen eine besondere Aufmerksamkeit gewidmet werden. Eine tiefe Kenntnis der Problemdomäne ist dabei von entscheidender Bedeutung.

Andere Sichten

Die oben beschriebenen beiden klassischen Muster sind eine lang etablierte Sicht auf die Architektur von Softwaresystemen. Die Detailimplementierungen mit ihren zahlreichen Facetten sind entsprechend gut dokumentiert. Die POSA-Serie [3] und Autoren wie z.B. Martin Fowler [4] zeigen für fast jede Problematik eine geeignete Lösung auf. Sie bieten einen großen Fundus an Erfahrungen, die jeder Architekt zumindest einmal gesichtet haben sollte, bevor er das Rad neu erfindet.

Es gibt aber auch andere Blickwinkel, aus denen man das Thema betrachten kann. So findet man gelegentlich den Begriff des Architekturstils insbesondere dann, wenn ein erweiterter Kontext betrachtet wird. So werden z.B. der RESTful-Ansatz (siehe Abbildung 4) oder auch Cloud Computing (siehe Abbildung 5) als Architekturstil bezeichnet.

afl abb4
Abb. 4: Architekturstil REST siehe Roy Fielding.

afl abb5
Abb. 5: Architekturstil Cloud Computing.

Bei anderen Autoren (z.B. Bosch, Shaw, Garlan) findet man aber auch Programmierparadigmen als Architekturstile wieder. Sie beschreiben z.B. den „objekt-orientierten Stil“ als die Organisation eines Systems in kommunizierende Objekte. Objekte sind Entitäten (mit Identität), die Daten (Status) und Operationen besitzen. Sie stellen Schnittstellen zur Verfügung, die von anderen Objekten verwendet werden können.

Die Schnittstellen werden durch das Versenden von Nachrichten angesprochen. Eine Nachricht bewirkt in einem Objekt die Ausführung einer Operation, die die Veränderung des internen Status und/oder das Versenden von Nachrichten an andere Objekte bewirkt.

Fazit

Egal vor welchem Problem man als Architekt oder Entwickler steht, es ist extrem selten, dass nicht jemand anderes auch schon darüber nachgedacht hat. Deshalb sollte vor dem Entwickeln einer eigenen Lösung immer ein Blick in die Schatzkiste der Architekturmuster und -stile geworfen werden. In der Regel findet sich dort eine passende Lösung, die sich bereits bewährt hat.

So lassen sich Fehler, aus denen andere Architekten bereits gelernt haben, vermeiden und die Qualität der zu entwickelnden Systeme steigern. In diesem Falle ist kopieren und abschauen durchaus erwünscht und erlaubt.