Softwarearchitektur verstehen und entscheiden

Aaron KreisÜber mich
Architekturstile

Microkernel Architecture

Kleiner Kern mit einsteckbaren Erweiterungen

Die Idee und ihr Nutzen

Eine Versicherung baut eine Plattform, die Kfz-, Hausrat- und Haftpflichtschäden prüft. Der Ablauf ist für alle drei Versicherungssparten ähnlich: Schaden annehmen, prüfen, Aufgaben verteilen. Die Prüfregeln aber unterscheiden sich stark. Bei Kfz-Schäden prüft die Versicherung zum Beispiel Werkstattrechnungen, bei Hausratschäden Kaufbelege. Deshalb erledigt ein gemeinsamer Programmteil, der Kern, den gleichen Ablauf für alle Sparten. Die Prüfregeln jeder Sparte stecken in einer eigenen Erweiterung, die an den Kern angeschlossen wird. Eine neue Sparte kommt als weitere Erweiterung hinzu, ohne dass der Kern geändert werden muss. Das gilt, solange der Kern der neuen Erweiterung alle Angaben übergeben kann, die sie braucht. Diese Bauweise heißt Microkernel Architecture, auf Deutsch Architektur mit kleinem Kern. Klein ist der Kern, weil er keine einzige Prüfregel einer Sparte enthält.

Bausteine: Kunde: meldet einen Schaden; Kern: gleicher Ablauf für alle Sparten: annehmen, Prüfung steuern, Aufgabe anlegen; Kfz-Erweiterung: Prüfregeln Kfz; Hausrat-Erweiterung: Prüfregeln Hausrat; Haftpflicht-Erweiterung: Prüfregeln Haftpflicht. Verbindungen: Kunde — Kern; Kern — Kfz-Erweiterung; Kern — Hausrat-Erweiterung; Kern — Haftpflicht-Erweiterung. Der Kern erledigt den gemeinsamen Ablauf; die Prüfregeln jeder Sparte stecken in einer eigenen Erweiterung.

Wie funktioniert es?

Ein Kunde meldet einen Wasserschaden an seinem Hausrat. Der Kern nimmt die Meldung an und liest die Sparte „Hausrat“. In einem Verzeichnis ist festgehalten, welche Erweiterung für welche Sparte zuständig ist. Der Kern fragt das Verzeichnis nach der zuständigen Erweiterung, und das Verzeichnis nennt die Hausrat-Erweiterung. Der Kern übergibt der Hausrat-Erweiterung den Prüfauftrag mit den Schadenangaben. Die Hausrat-Erweiterung prüft den Wertnachweis, also die Belege über den Wert der beschädigten Sachen. Hier sind das die Kaufbelege für die Waschmaschine, die der Wasserschaden beschädigt hat. Die Hausrat-Erweiterung antwortet „Wertnachweis vollständig“, und der Kern legt eine Aufgabe für die zuständige Sachbearbeiterin an. Eine Kfz-Meldung gäbe der Kern auf dieselbe Weise an die Kfz-Erweiterung, die Werkstattrechnungen prüft. Eine Haftpflichtmeldung ginge an die Haftpflicht-Erweiterung, die die Forderung der geschädigten Person prüft.

Der Kern erledigt alles, was für alle Sparten gleich ist: das Annehmen einer Schadenmeldung, den Ablauf der Prüfung und die Verwaltung der Aufgaben für die Sachbearbeiter. Der Kern legt außerdem die gemeinsame Schnittstelle fest. Das ist die Vereinbarung, in welcher Form der Kern einer Erweiterung einen Prüfauftrag übergibt und in welcher Form er das Ergebnis zurückerwartet. Jede Erweiterung enthält, was je Sparte verschieden ist, hier die Prüfregeln. Jede Erweiterung bietet die gemeinsame Schnittstelle an, und der Kern ruft jede Erweiterung über diese Schnittstelle auf. Solche einsteckbaren Erweiterungen heißen auch Plugins.

Die Erweiterungen sind nicht fest in den Kern eingebaut. Ein Team baut eine Erweiterung, die die gemeinsame Schnittstelle anbietet, und meldet sie beim Kern an. Dabei trägt sich die Erweiterung mit ihrer Sparte in das Verzeichnis ein. Erweiterungen tauschen nur mit dem Kern Daten aus, nicht untereinander. Kern und Erweiterungen laufen in derselben Anwendung.

Findet der Kern für eine Sparte keine Erweiterung oder meldet die zuständige Erweiterung einen Fehler, behandelt der Kern die Meldung nach einer festgelegten Regel. Er legt die Meldung zum Beispiel einer Sachbearbeiterin zur Prüfung von Hand vor.

Microkernel: der Kern gibt jede Sparte an ihre Erweiterung

Beteiligte: Kunde: meldet einen Wasserschaden am Hausrat; Kern: nimmt Meldungen an, steuert die Prüfung, legt Aufgaben an; Verzeichnis: welche Erweiterung für welche Sparte; Hausrat-Erweiterung: prüft Wertnachweise; Kfz-Erweiterung: prüft Werkstattrechnungen; Sachbearbeiterin: erhält die Aufgabe. Ablauf: 1. Der Kunde meldet einen Wasserschaden an seinem Hausrat, und der Kern nimmt die Meldung an. 2. Der Kern liest die Sparte „Hausrat“ und fragt das Verzeichnis nach der zuständigen Erweiterung. 3. Das Verzeichnis nennt dem Kern die Hausrat-Erweiterung, die sich dort angemeldet hat. 4. Über die gemeinsame Schnittstelle übergibt der Kern der Hausrat-Erweiterung den Prüfauftrag mit den Schadenangaben. 5. Die Hausrat-Erweiterung prüft die Kaufbelege der beschädigten Waschmaschine und antwortet „Wertnachweis vollständig“. 6. Der Kern legt eine Aufgabe für die zuständige Sachbearbeiterin an. 7. Eine Kfz-Meldung gäbe der Kern auf dieselbe Weise an die Kfz-Erweiterung. Eine neue Sparte bekommt eine neue Erweiterung, solange die gemeinsame Schnittstelle ausreicht.

Wann ist es sinnvoll und wann nicht?

Eine Microkernel Architecture lohnt sich

  • Ein gemeinsamer Ablauf, der sich selten ändert, gilt für viele fachliche Varianten mit eigenen Regeln. Beispiele sind Tarifberechnung, Schadenprüfung und Steuerberechnung. Ohne Microkernel Architecture steht der gemeinsame Ablauf entweder in jeder Variante noch einmal oder zusammen mit allen Regeln in einem Code voller Fallunterscheidungen.
  • Später sollen weitere Sparten hinzukommen. Bietet die Versicherung zum Beispiel eine Tierkrankenversicherung an, baut ein Team eine vierte Erweiterung, die Tierarztrechnungen prüft, und der Kern bleibt unverändert. Ohne Erweiterungen müssten die Entwickler die Prüfung der Tierarztrechnungen in den gemeinsamen Code einbauen, der auch die anderen drei Sparten prüft. Braucht die Tierkrankenversicherung allerdings einen zusätzlichen Prüfschritt, den die gemeinsame Schnittstelle nicht vorsieht, müssen Schnittstelle und Kern erweitert werden.
  • Ein Fehler im gemeinsamen Ablauf soll nur einmal behoben werden müssen. Ein Fehler in der Aufgabenverteilung zum Beispiel steckt nur im Kern und wird dort für alle Sparten zugleich behoben. Baut die Versicherung stattdessen für jede Sparte eine eigene Anwendung mit kopiertem Code, muss sie den Fehler in jeder dieser Anwendungen einzeln beheben, und mit der Zeit entwickeln sich die Anwendungen auseinander.
  • Die Prüfregeln einer Sparte sollen sich ändern lassen, ohne die anderen Sparten zu gefährden. Eine Änderung an den Hausrat-Prüfregeln betrifft nur den Code der Hausrat-Erweiterung. Stecken dagegen alle Sparten in einem einzigen Code mit Fallunterscheidungen wie „wenn Kfz, dann Werkstattrechnung prüfen, wenn Hausrat, dann Wertnachweis prüfen“, wird jede Fallunterscheidung mit jeder Sparte länger, und jede Änderung an den Regeln einer Sparte kann versehentlich die anderen Sparten stören.
  • Die Prüfregeln ändern sich häufiger als der gemeinsame Ablauf und können von verschiedenen Teams betreut werden. Das Team der Hausrat-Erweiterung testet seine Erweiterung, indem es ihr Prüfaufträge in der Form der gemeinsamen Schnittstelle übergibt, ohne den Kern und die anderen Erweiterungen zu starten. Stünden alle Prüfregeln in einem gemeinsamen Code, müsste das Team für jeden Test die ganze Anwendung mit allen Sparten starten.
  • Kunden oder Partner sollen eine Anwendung selbst erweitern. Ein Beispiel ist die Entwicklungsumgebung Eclipse, für die viele andere Hersteller Erweiterungen bauen. Ohne Erweiterungen könnte nur der Hersteller der Anwendung selbst etwas ergänzen.

Eine Microkernel Architecture ist nicht das Richtige

  • Die Varianten unterscheiden sich so stark, dass kaum ein gemeinsamer Ablauf übrig bleibt. Der Kern hätte dann kaum etwas zu erledigen, und die gemeinsame Schnittstelle müsste die Angaben jedes einzelnen Ablaufs vorsehen. Dann teilen die Entwickler die Anwendung nach Sparten in getrennte Teile, und jeder Teil enthält seinen eigenen Ablauf. Dieses Vorgehen heißt Modular Monolith. Ein Kern, eine gemeinsame Schnittstelle und ein Verzeichnis, die zu keinem der Abläufe passen, entfallen.
  • Es gibt nur eine Sparte, und ihre Prüfregeln ändern sich selten. Der Kern hätte dann nur eine einzige Erweiterung aufzurufen. Dann schreiben die Entwickler Ablauf und Prüfregeln in einen gemeinsamen Code. Kern, gemeinsame Schnittstelle und Verzeichnis, die entworfen, betrieben und überwacht werden müssen, entfallen.
  • Die Sparten unterscheiden sich nur in wenigen Regeln. Dann schreiben die Entwickler die wenigen abweichenden Regeln als Fallunterscheidung in den gemeinsamen Code, etwa „wenn Kfz, dann Werkstattrechnung prüfen“. Bei wenigen Regeln bleiben diese Fallunterscheidungen kurz und überschaubar. Eine gemeinsame Schnittstelle und ein Verzeichnis, die entworfen und gepflegt werden müssen, entfallen. Werden die Fallunterscheidungen mit jeder neuen Sparte länger, lohnt sich der Umbau auf Erweiterungen.
  • Die Prüfungen verschiedener Sparten müssen ständig Ergebnisse untereinander austauschen. Ein Beispiel: Die Haftpflicht-Erweiterung braucht für ihre Prüfung laufend Zwischenergebnisse der Hausrat-Erweiterung. Erweiterungen tauschen aber nur mit dem Kern Daten aus, und der Kern müsste jedes Zwischenergebnis der Hausrat-Erweiterung an die Haftpflicht-Erweiterung weiterreichen. Dann fassen die Entwickler die Hausratprüfung und die Haftpflichtprüfung in einem gemeinsamen Programmteil zusammen, der die Zwischenergebnisse selbst weitergibt.

Nachteile und typische Fehler

Die gemeinsame Schnittstelle ist schwer richtig zu entwerfen, weil sie schon die Angaben vorsehen muss, die spätere Sparten brauchen. Ändert sich die Schnittstelle, müssen alle Erweiterungen gleichzeitig angepasst werden. Das Verzeichnis und das Laden der Erweiterungen müssen betrieben und überwacht werden, und die Versionen der Erweiterungen müssen verwaltet werden. Die Bauweise schirmt den Kern nicht von selbst gegen eine fehlerhafte Erweiterung ab. Soll eine fehlerhafte Erweiterung den Kern nicht stören, muss sie abgeschirmt laufen, etwa mit einem Zeitlimit für jede Prüfung oder als eigenes, vom Kern getrenntes Programm. Diese Abschirmung bedeutet zusätzlichen Aufwand und macht jede Prüfung etwas langsamer.

Typische Fehler und woran man sie erkennt:

  • Zu enge Schnittstelle: Die gemeinsame Schnittstelle transportiert nicht alle Angaben, die eine neue Sparte braucht. Sie übergibt zum Beispiel die Schadenangaben, aber keine Vertragsdaten. Eine neue Erweiterung für die Tierkrankenversicherung braucht den versicherten Höchstbetrag aus dem Vertrag. Das Team der Erweiterung lässt die Erweiterung den Höchstbetrag deshalb direkt aus der Datenbank des Kerns lesen. Erkennbar ist das an Erweiterungen, die fehlende Angaben auf Umwegen beschaffen, etwa direkt aus der Datenbank des Kerns.
  • Zu weite Schnittstelle: Erweiterungen können über die Schnittstelle den Ablauf des Kerns unkontrolliert beeinflussen. Die Schnittstelle erlaubt einer Erweiterung zum Beispiel, dem Kern „Ablauf beenden“ zu melden. Die Kfz-Erweiterung meldet das versehentlich bei jeder Werkstattrechnung unter 500 Euro. Der Kern legt für diese Kfz-Meldungen dann keine Aufgabe für die Sachbearbeiter an. Erkennbar ist das an Fehlern im Ablauf des Kerns, die nur bei Meldungen einer bestimmten Sparte auftreten.
  • Nicht abgeschirmte Erweiterung: Eine Erweiterung läuft nicht abgeschirmt. Die Haftpflicht-Erweiterung gerät zum Beispiel bei einer bestimmten Forderung in eine Endlosschleife. Kern und Erweiterungen laufen in derselben Anwendung, und die Schleife belegt die Rechenzeit dieser Anwendung. Der Kern kommt dann auch mit Kfz- und Hausratmeldungen nicht mehr voran. Erkennbar ist das an Meldungen aller Sparten, die liegen bleiben, obwohl nur eine Erweiterung fehlerhaft ist.
  • Wachsender Kern: Der Kern nimmt immer mehr fachliche Regeln auf. Ein Team baut zum Beispiel eine Sonderregel für Kfz-Schäden über 10.000 Euro direkt in den Kern ein, weil das ohne Änderung der gemeinsamen Schnittstelle geht. Mit jeder solchen Regel wird der Kern mehr zu dem Programmteil, den fast jede Änderung berührt. Erkennbar ist das an Fallunterscheidungen nach Sparte im Code des Kerns.

Weiterführend

Verwandte Ansätze

  • Plugin Architecture: beschreibt allgemein, wie Anwendungen über Erweiterungspunkte und nachladbare Erweiterungen erweitert werden. Bei der Microkernel Architecture ist der Kern bewusst klein und stellt nur die gemeinsamen Grundaufgaben bereit.
  • Inversion of Control: beschreibt, dass ein Rahmenprogramm den Code der Anwendung aufruft, so wie der Kern die Erweiterungen aufruft.
  • Separation of Concerns: der Grundsatz, jede Aufgabe in genau einem Programmteil zu erledigen. Bei der Microkernel Architecture werden gemeinsame Grundaufgaben und fachliche Varianten getrennt.

Werkzeuge und Umsetzungen

  • Eclipse Equinox: Umsetzung von OSGi, einem Java-Standard für Programmteile, die sich während des Betriebs laden und austauschen lassen. Die Entwicklungsumgebung Eclipse ist auf dieser Grundlage aus Kern und Erweiterungen aufgebaut.
  • Apache Felix: eine weitere Umsetzung von OSGi für Java-Anwendungen aus Kern und Erweiterungen.
  • PF4J: Java-Bibliothek, mit der sich Anwendungen mit einsteckbaren Erweiterungen bauen lassen.

Quellen