Be Software Architecture Wise
Be Wise Apps

Read in English · Leer en español · Lire en français

Abschnitt 5.5 von 5.8 · Mittelstufe · 24 Min. Lesezeit

Microkernel- und Plug-in-Architekturen

Videolektion

4 Min. · auf Englisch gesprochen, mit Untertiteln
Nur anhören (Audio)
Transkript lesen

Viele erfolgreiche Produkte sind ein kleiner Kern, den andere erweitern. In dieser Lektion lernen Sie, wie der Microkernel- oder Plug-in-Stil funktioniert, wo er passt und wie Sie seine Verträge über Jahre gesund halten.

Denken Sie an eine Spielkonsole. Die Konsole selbst erledigt ein paar allgemeine Dinge: Sie startet, zeigt ein Menü, liest die Controller aus und zeichnet Bilder. Jedes Spiel ist ein Modul, das über einen Standardanschluss eingesteckt wird. Sie können jahrelang neue Spiele hinzufügen, ohne die Konsole zu öffnen, und ein schlecht gemachtes Spiel kann sie nicht umverdrahten. Eine Microkernel-Architektur baut Software auf dieselbe Weise.

So sieht der Aufbau aus. Das Kernsystem enthält die minimale, allgemeine Logik: den Hauptablauf und die Stellen, an denen Plug-ins andocken. Im Checkout einer Essenslieferplattform heißt das: einen Warenkorb bepreisen, die Zahlung entgegennehmen und die Bestellung bestätigen. Im Kern hält eine Registry fest, welche Plug-ins es gibt, ihre Versionen und den Vertrag, den jedes implementiert. Damit kann der Kern nach den Steuerregeln für Deutschland fragen, ohne zu wissen, welche Klasse sie liefert. Plug-ins sind nur über Verträge angebunden, die der Kern veröffentlicht. Auf der einen Seite Steuerregeln für Großbritannien, Deutschland und die Vereinigten Staaten. Auf der anderen Zahlungsmethoden: Karten, Wallets und Barzahlung bei Lieferung. Zwei Regeln halten das Ganze zusammen. Plug-ins rufen sich nie gegenseitig auf, und der Kern nennt nie ein bestimmtes Plug-in.

Der Stil passt zu Produkten mit einem stabilen Ablauf, aber vielen Varianten bestimmter Schritte. Steuer- und Lohnregeln je Land, Versicherungsregeln je Rechtsordnung, Zahlungsmethoden, Datei-Importer. Er passt auch zu Plattformen, die ein Ökosystem von Erweiterungen Dritter wollen. Entwicklungswerkzeuge wie Eclipse und Visual Studio Code und auch Webbrowser sind so gebaut. Visual Studio Code führt Extensions sogar in einem separaten Prozess aus, damit eine fehlerhafte den Editor nicht einfrieren kann.

Entwerfen wir einen. Die Essenslieferplattform startet nach Großbritannien in Deutschland und den Vereinigten Staaten, und ihr Checkout steckt bereits voller Verzweigungen für jedes Land. Benennen Sie zuerst den Variationspunkt. Nur die Steuer auf einen bepreisten Warenkorb variiert je Land. Preisgestaltung, Zahlung und Belege bleiben im Kern, was den Vertrag klein hält. Legen Sie als Nächstes fest, was den Vertrag überquert. Hinein gehen Warenkorbpositionen, jede mit einer neutralen Kategorie wie warmes Essen oder Liefergebühr. Heraus kommen die Gesamtsteuer und eine beschriftete Aufschlüsselung. Es werden keine Bestellungen, Kunden oder Datenbanksitzungen übergeben, sodass Plug-ins nicht von Interna des Kerns abhängen können. Der Vertrag wird zu einem kleinen, separat versionierten Paket. Jedes Plug-in deklariert die Version, die es braucht, und die Registry prüft beim Start, sodass eine Abweichung beim Deployment auffällt, nicht als falsche Steuer auf einem Beleg. Gibt es für ein Land kein Plug-in, lehnt der Checkout die Bestellung ab, statt null Steuern zu berechnen. Schließlich läuft eine gemeinsame Contract-Test-Suite gegen jedes Plug-in. Ein viertes Land hinzuzufügen ist jetzt ein neues Paket mit Tests, keine Änderung am Checkout.

So sieht es zur Laufzeit aus. Der Checkout fragt die Registry nach dem für Deutschland registrierten Plug-in und bekommt es zurück. Dann ruft er dieses Plug-in über den Vertrag auf. Beachten Sie das Plug-in für die Vereinigten Staaten. Es wurde für eine ältere Major-Version des Vertrags gebaut, daher hat die Registry es beim Start mit einer Log-Meldung übersprungen, statt den Checkout abstürzen zu lassen.

Drei Dinge halten eine Plug-in-Architektur gesund. Erstens: Isolation. Prozessinterne Plug-ins sind schnell, aber eines mit einem Speicherleck betrifft alles. Die Steuerregeln Ihres eigenen Teams können prozessintern laufen; beliebiger Code aus einem öffentlichen Marktplatz sollte das nicht. Zweitens: Versionierung. Bevorzugen Sie additive Änderungen wie neue optionale Felder, damit alte Plug-ins weiter funktionieren. Drittens: Hyrums Gesetz: Bei genügend Nutzern verlässt sich irgendjemand auf jedes beobachtbare Verhalten einer Schnittstelle. Halten Sie den Vertrag also klein, und behandeln Sie alles, was Plug-ins sehen können, als Teil davon. Und bauen Sie keine Erweiterungspunkte für erdachte Bedürfnisse. Warten Sie, bis Sie dieselbe Art von Variation zwei- oder dreimal gebaut haben, und leiten Sie den Vertrag dann aus den echten Fällen ab.

Der vollständige Abschnitt erklärt, wie sich das von einem Betriebssystem-Microkernel unterscheidet, listet die häufigen Anti-Patterns auf und zeigt Vertrag und Registry im Code. Als Nächstes, in Abschnitt fünf Punkt sechs: hexagonale, Onion- und Clean Architecture. Bis gleich.

Was Sie wissen müssen

Definition und Ursprung

Eine Microkernel-Architektur, auch Plug-in-Architektur genannt, teilt eine Anwendung in zwei Arten von Teilen: ein Kernsystem, das die minimale, allgemeine Funktionalität enthält, die für den Betrieb nötig ist, und Plug-ins, die Verhalten hinzufügen oder ändern, ohne dass der Kern verändert wird. Plug-ins sind nur über veröffentlichte Verträge an den Kern angebunden, und der Kern findet sie über eine Registry. Mark Richards und Neal Ford führen sie in Fundamentals of Software Architecture als einen der klassischen Architekturstile, und sie passt zur Beschreibung eines Architekturstils in Abschnitt 5.1: eine benannte Menge von Einschränkungen für die Elemente (Kern, Plug-ins) und Konnektoren (Verträge) eines Systems.

Die Idee entstand in Produktsoftware: Werkzeugen, die von vielen Kunden installiert werden, von denen jeder etwas leicht anderes möchte. Statt für jeden Kunden eine eigene Version zu pflegen, liefert der Hersteller einen Kern aus und lässt Kunden, Partner oder die eigenen Teams Plug-ins hinzufügen. Integrierte Entwicklungsumgebungen, Webbrowser, Grafikprogramme und Buchhaltungspakete funktionieren alle so. Der Name ist von Betriebssystemen entlehnt, wo ein Microkernel ein sehr kleiner Kernel ist, außerhalb dessen alles andere läuft; die beiden Ideen sind verwandt, sollten aber nicht verwechselt werden, wie weiter unten erklärt.

Eine Microkernel-Anwendung ist normalerweise eine einzige Deployment-Einheit und gehört daher zu den monolithischen Stilen dieses Kapitels: eine Deployment-Einheit, ein Prozess, ein Satz betrieblicher Anliegen. Plug-ins können separat installiert werden, aber zur Laufzeit leben sie im Kern oder direkt daneben.

Der Aufbau eines Microkernel-Systems

Eine Microkernel-Architektur: ein Kernsystem mit einer Plug-in-Registry und Plug-ins, die nur über veröffentlichte Verträge an den Kern angebunden sindEine deploybare Anwendung (Plug-ins beim Start oder zur Laufzeit geladen)Kernsystemminimale, allg. LogikPlug-in-RegistryID, Version, VertragVertragVertragPlug-in ASteuern UKPlug-in BSteuern DEPlug-in CUS-Sales-TaxPlug-in DKartenzahlungPlug-in EWallet-ZahlungPlug-in FBarzahlungPlug-ins rufen sich nie gegenseitig auf; der Kern nennt kein Plug-inPfeile: Jedes Plug-in implementiert einen Vertrag des Kerns
Das Kernsystem besitzt den allgemeinen Ablauf und eine Registry; jedes Plug-in implementiert einen Vertrag, den der Kern veröffentlicht, und Plug-ins hängen nie voneinander ab.

So funktioniert es: Kern, Plug-ins, Registry und Verträge

Das Kernsystem enthält, was jede Installation braucht: den Hauptablauf, gemeinsame Dienste wie Logging oder Konfiguration und die Erweiterungspunkte, an denen Plug-ins andocken. Richards und Ford beschreiben es als den Happy Path durch die Anwendung, mit wenig oder keiner kundenspezifischen Verarbeitung; im Checkout einer Essenslieferplattform heißt das: einen Warenkorb bepreisen, die Zahlung entgegennehmen und die Bestellung bestätigen. Ein guter Kern ist bewusst langweilig. Jeder Sonderfall, der je nach Kunde, Land oder Produktlinie variiert, ist ein Kandidat dafür, aus ihm herausgelöst zu werden.

Plug-ins sind unabhängige, in sich geschlossene Module mit spezialisierter Verarbeitung. Sie sollten nur vom Vertrag abhängen, nicht voneinander und nicht von den internen Klassen des Kerns. Braucht ein Plug-in etwas, das ein anderes bereitstellt, sollte der Kern diesen Bedarf decken (als Dienst, den er über den Vertrag anbietet), oder die beiden Plug-ins sollten zusammengelegt werden; Abhängigkeiten zwischen Plug-ins verwandeln einen sauberen Stern unbemerkt in ein Knäuel.

Die Registry hält fest, welche Plug-ins verfügbar sind, wie jedes heißt, welche Version es hat, welchen Vertrag es implementiert und wie es erreichbar ist. Sie kann so einfach sein wie ein beim Start gefülltes Dictionary, ein Verzeichnis, das der Kern nach Manifesten durchsucht, Einträge in den Metadaten eines Pakets (wie bei Python-Entry Points), eine Datenbanktabelle oder ein entfernter Dienst, der anderswo deployte Plug-ins auflistet. Die Registry ermöglicht es dem Kern, „Gib mir die Steuerregeln für Deutschland“ zu sagen, ohne zu wissen, dass eine Klasse namens GermanTax existiert.

Verträge legen fest, was ein Plug-in bereitstellen muss und worauf es sich verlassen darf: eine Schnittstelle mit Methodensignaturen, die Datenstrukturen, die sie überqueren, das Fehlerverhalten und eine Version. In Eclipse ist ein Vertrag ein Erweiterungspunkt, der in einem Plug-in-Manifest mit einem Schema deklariert wird; in Visual Studio Code ist es die Extension-API plus die Contribution Points, die in der package.json einer Extension aufgeführt sind; in Ihrem eigenen Code ist es meist eine Schnittstelle (oder ein Python-Protocol) in einem eigenen kleinen Paket. Stammt das Plug-in von einer anderen Organisation und kann es Ihrem Vertrag nicht folgen, übersetzt ein Adapter zwischen beiden, genau wie in der hexagonalen Architektur.

Plug-ins lassen sich auf drei Arten anbinden. Punkt-zu-Punkt-Aufrufe im selben Prozess sind der Normalfall: Der Kern ruft eine Methode des Plug-in-Objekts auf. Plug-ins in getrennten Prozessen laufen in einem eigenen Prozess und sprechen über Interprozessnachrichten mit dem Kern, was den Kern vor Abstürzen schützt. Entfernte Plug-ins sitzen hinter einer HTTP- oder Messaging-Schnittstelle; das erlaubt Plug-ins in anderen Sprachen oder im Besitz anderer Teams, bringt aber Netzwerklatenz und neue Ausfallarten mit sich, und das System ähnelt allmählich den verteilten Stilen aus Kapitel 6.

Echte Beispiele

Eclipse. Die Eclipse-IDE ist das Lehrbuchbeispiel. Ihr Kern ist kaum mehr als eine Workbench und eine Plug-in-Laufzeitumgebung (aufgebaut auf Equinox, einer Implementierung der OSGi-Framework-Spezifikation). Fast alles, was Entwicklungsteams sehen, von den Java-Werkzeugen bis zur Integration der Versionsverwaltung, ist ein Plug-in. Jedes Plug-in hat ein Manifest, das deklariert, was es braucht, welche Erweiterungspunkte es anderen anbietet und in welche Erweiterungen es sich einklinkt. Plug-ins werden verzögert aktiviert, erst wenn tatsächlich etwas sie braucht, damit Hunderte installierter Plug-ins den Start nicht verlangsamen.

Visual Studio Code. Extensions deklarieren Aktivierungsereignisse (eine Markdown-Datei öffnen, einen Befehl ausführen), damit sie nur bei Bedarf geladen werden, und steuern über ihr Manifest Befehle, Sprachen, Themes und Ansichten bei. Extensions laufen in einem separaten Extension-Host-Prozess, sodass eine fehlerhafte Extension die Benutzeroberfläche des Editors nicht einfrieren kann. Das ist ein gutes Beispiel dafür, Prozessisolation für ein Plug-in-Ökosystem mit Tausenden externer Autoren zu wählen.

Webbrowser. Browser-Extensions nutzen einen Standardsatz von Extension-APIs und müssen die benötigten Berechtigungen (welche Websites sie lesen dürfen, ob sie Tabs oder Downloads sehen dürfen) in einem Manifest deklarieren. Der Browser setzt diese Berechtigungen durch, weil man den Autoren von Extensions nicht von vornherein vertrauen kann.

Business-Rules-Engines. Das Schadensystem eines Versicherers kann den Ablauf der Schadenbearbeitung im Kern halten und die Regeln jeder Rechtsordnung (was gedeckt ist, welche Unterlagen nötig sind, gesetzliche Fristen) in Plug-ins auslagern. Steuer-Engines, Lohnabrechnungssysteme und E-Commerce-Plattformen mit Plug-ins für Zahlungsmethoden folgen demselben Muster: ein stabiler Prozess mit Regeln oder Anbietern, die je nach Land, Produkt oder Kunde variieren.

Entwicklerwerkzeuge. Das Test-Framework pytest ist um die Plug-in-Bibliothek pluggy herum gebaut; viele seiner eigenen Funktionen sind als Plug-ins implementiert, mit denselben Hooks, die auch Dritte nutzen. Build-Tools, Linter und Static-Site-Generatoren machen es häufig genauso.

Anwendungs-Microkernel und Betriebssystem-Microkernel im Vergleich

AspektBetriebssystem-MicrokernelAnwendungs-Microkernel (Plug-in-Architektur)
Was minimal istDer Kernel: Adressräume, Threads, Scheduling und InterprozesskommunikationDas Kernsystem: der allgemeine Ablauf einer Anwendung
Was außerhalb des Kerns liegtGerätetreiber, Dateisysteme und Netzwerkstacks, die als separate Server im User Space laufenVariables fachliches Verhalten oder Features, etwa Steuerregeln, Zahlungsmethoden oder Editor-Sprachen
HauptzieleZuverlässigkeit, Sicherheit und eine kleine Trusted Computing BaseErweiterbarkeit, Anpassbarkeit und ein Ökosystem von Drittanbietern
Wie die Teile kommunizierenNachrichtenaustausch zwischen isolierten ProzessenMeist prozessinterne Aufrufe über eine Schnittstelle; manchmal ein separater Prozess oder ein entfernter Aufruf
BeispieleMach, die L4-Familie (einschließlich des formal verifizierten seL4), QNX, MINIX 3Eclipse, Visual Studio Code, Webbrowser, pytest, Regel-Engines für Versicherungen und Steuern

Isolation, Versionierung und Kompatibilität von Plug-ins

Drei Fragen entscheiden, ob eine Plug-in-Architektur über Jahre gesund bleibt: wie isoliert Plug-ins sind, wie sich der Vertrag weiterentwickelt und wie Inkompatibilität erkannt wird.

Isolation. Prozessinterne Plug-ins teilen Speicher, Threads und Berechtigungen des Kerns. Das ist schnell und einfach, aber ein einziges Plug-in mit einer Endlosschleife, einem Speicherleck oder einer Sicherheitslücke betrifft alles. Die Optionen, nach zunehmender Isolation und zunehmenden Kosten geordnet, sind: prozessintern mit defensiven Aufrufen (Timeouts, Abfangen von Exceptions je Plug-in, Ressourcenlimits); ein separater Prozess oder Worker je Plug-in oder Gruppe von Plug-ins; eine Sandbox wie eine WebAssembly-Laufzeitumgebung oder ein eingeschränkter Interpreter, die begrenzt, worauf Plug-in-Code zugreifen kann; und ein entfernter Dienst. Wählen Sie, indem Sie fragen, wer die Plug-ins schreibt. Die Steuerregeln Ihres eigenen Teams können prozessintern laufen; beliebiger Code aus einem öffentlichen Marktplatz sollte das nicht.

Versionierung. Der Vertrag ist eine API, und es gelten die Regeln aus Abschnitt 0.6 zu Breaking Changes. Versionieren Sie den Vertrag getrennt vom Kern, meist mit semantischer Versionierung: eine neue Major-Version für Breaking Changes, eine Minor-Version für abwärtskompatible Ergänzungen. Lassen Sie jedes Plug-in deklarieren, für welche Vertragsversion es gebaut wurde, und den Kern, welche Versionen er unterstützt. Additive Änderungen (neue optionale Felder, neue optionale Hooks) halten alte Plug-ins lauffähig; ist eine Breaking Change unvermeidlich, unterstützen Sie die alte und die neue Major-Version für eine veröffentlichte Deprecation-Frist parallel.

Kompatibilitätsprüfungen. Prüfen Sie die Kompatibilität beim Laden der Plug-ins, nicht erst, wenn ein Kunde sie zum ersten Mal auslöst. Eine Registry, die ein inkompatibles Plug-in beim Start mit einer klaren Log-Meldung ablehnt, macht aus einem Laufzeitfehler einen fehlgeschlagenen Deployment-Schritt. Die Beobachtung von Hyrum Wright, bekannt als Hyrums Gesetz, sollte man hier im Kopf behalten: Bei genügend Nutzern wird sich irgendjemand auf jedes beobachtbare Verhalten einer Schnittstelle verlassen, ganz gleich, was in der Dokumentation steht. Halten Sie den Vertrag klein, halten Sie die Interna des Kerns außer Reichweite (Eclipse kennzeichnet Nicht-API-Pakete aus diesem Grund als internal) und behandeln Sie alles, was für Plug-ins sichtbar ist, als Teil des Vertrags.

Merkmale des Microkernel-Stils

MerkmalBewertungBegründung
Erweiterbarkeit und Änderbarkeit (in den geplanten Richtungen)HochNeues Verhalten kommt als neues Plug-in hinzu; der Kern und die anderen Plug-ins ändern sich nicht.
EinfachheitMittelDie Idee ist einfach, doch Verträge, eine Registry, das Laden und die Versionsprüfungen sind echte Mechanik.
TestbarkeitMittel bis hochJedes Plug-in lässt sich allein gegen den Vertrag testen; der Kern lässt sich mit Fake-Plug-ins testen. Kombinationen müssen trotzdem getestet werden.
DeploybarkeitMittelPlug-ins lassen sich oft hinzufügen, ohne den Kern neu zu deployen; eine Vertragsänderung erzwingt koordinierte Releases.
PerformanceMittel bis hochProzessinterne Plug-ins sind gewöhnliche Funktionsaufrufe; Isolation per Prozess oder Sandbox verursacht Overhead.
Skalierbarkeit und ElastizitätNiedrigMeist ist es eine einzige Deployment-Einheit und skaliert daher als Ganzes, wie jeder Monolith.
FehlertoleranzNiedrig bis mittelEin prozessinternes Plug-in kann den Host zum Absturz bringen oder verlangsamen; Prozessisolation verbessert das zu einem Preis.
GesamtkostenHoch (günstig)Eine Codebasis und eine Laufzeitumgebung im Betrieb; die Hauptkosten liegen im Entwerfen und Weiterentwickeln der Verträge.

Wann man ihn einsetzt und wann nicht

Gute Einsatzfälle. Produktsoftware, die viele Kunden installieren und anpassen. Systeme mit einem stabilen Ablauf, aber vielen Varianten eines Schritts: Steuern und Lohnabrechnung je Land, Versicherungsregeln je Rechtsordnung, Zahlungsmethoden, Importer für Dateiformate, Versanddienstleister. Plattformen, die ein Ökosystem von Drittanbietern wollen. Systeme, in denen eine Aufsichtsbehörde oder ein Kunde häufig Regeln ändert und Sie diese Änderungen als kleine, separat getestete Releases haben wollen. Der Stil funktioniert auch innerhalb eines größeren Systems: Ein Service in einer verteilten Architektur kann selbst ein Microkernel sein.

Schlechte Einsatzfälle. Systeme, bei denen Sie noch nicht wissen, was variiert. Im Voraus für erdachte Bedürfnisse entworfene Erweiterungspunkte sind spekulative Allgemeinheit: Sie fügen Mechanik hinzu, bremsen das Team und sitzen meist an der falschen Stelle, wenn der echte Bedarf kommt (siehe die Diskussion der Umkehrbarkeit in Abschnitt 1.6). Systeme, die sehr hohe Skalierbarkeit oder Fehlerisolation brauchen, da das Ganze als eine Einheit skaliert und ausfällt. Systeme, in denen Plug-ins eng miteinander koordiniert werden müssten, was den Hauptnutzen des Stils zunichtemacht.

Eine praktische Regel: Warten Sie, bis Sie dieselbe Art von Variation zwei- oder dreimal gebaut haben, und leiten Sie den Vertrag dann aus den echten Fällen ab. Ein Vertrag, der aus drei funktionierenden Steuerimplementierungen abgeleitet ist, wird weit besser sein als einer, der vor der ersten geraten wurde.

Ausgearbeitetes Beispiel

Ausgearbeitetes Beispiel: einen Plug-in-Vertrag für länderspezifische Steuerregeln entwerfen

Die Essenslieferplattform, die sich durch diesen Kurs zieht, startet nach dem Vereinigten Königreich in Deutschland und den Vereinigten Staaten. Jedes Land besteuert Speisen, Alkohol und Liefergebühren anders, und die US-Sales-Tax variiert zusätzlich nach Bundesstaat und Stadt. Der Checkout-Code enthält bereits Verzweigungen der Form if country == "GB". Der Architekt schlägt einen Microkernel vor: Der Checkout ist der Kern, und die Regeln jedes Landes sind ein Plug-in. Dies sind die Entscheidungen in der Reihenfolge, in der sie getroffen wurden.

1. Den Variationspunkt benennenNur die Berechnung der Steuer auf einen bepreisten Warenkorb variiert je Land. Preisgestaltung, Rabatte, Zahlung und Belege bleiben im Kern. Ein schmaler Variationspunkt hält den Vertrag klein.
2. Festlegen, was den Vertrag überquertEingaben: Warenkorbpositionen mit einer neutralen Kategorie (hot-food, alcohol, delivery-fee), Nettobeträge als Decimal und eine optionale Region. Ausgabe: die Gesamtsteuer und eine beschriftete Aufschlüsselung für den Beleg. Es werden keine Kernobjekte (Bestellungen, Kunden, Datenbanksitzungen) übergeben, sodass Plug-ins nicht von Interna des Kerns abhängen können.
3. Den Vertrag zu einem eigenen Paket machentax_contract enthält nur Datenklassen und das Protokoll TaxRules und wird mit semantischer Versionierung versioniert. Kern und Plug-ins hängen beide davon ab; keiner hängt vom anderen ab.
4. Kompatibilität definierenEin Plug-in deklariert die Vertragsversion, die es braucht, etwa (2, 1). Der Kern akzeptiert es, wenn die Major-Version übereinstimmt und die Minor-Version des Kerns mindestens so hoch ist. Ein optionales Feld hinzuzufügen ist eine Minor-Änderung; eines umzubenennen oder zu entfernen ist eine Major-Änderung.
5. Die Erkennung wählenPlug-ins sind gewöhnliche Python-Pakete, die sich unter der Entry-Point-Gruppe fooddelivery.tax_rules registrieren. Ein Paket zu installieren genügt; kein Kerncode listet die Länder auf.
6. Das Fehlerverhalten festlegenIst für ein Land kein Plug-in registriert, lehnt der Checkout die Bestellung mit einem klaren Fehler ab, statt null Steuern zu berechnen. Ein Plug-in, das eine Exception auslöst, lässt nur diesen einen Bezahlvorgang scheitern, und der Fehler wird mit Name und Version des Plug-ins gemeldet.
7. Die Isolation festlegenDie Plug-ins werden von den eigenen Länderteams des Unternehmens geschrieben und gelten als vertrauenswürdig, daher laufen sie wegen der Geschwindigkeit prozessintern. Dürften externe Steueranbieter Plug-ins liefern, würde das Design sie hinter einen entfernten Aufruf mit Timeout verlagern.
8. Die Tests festlegenEine gemeinsame Contract-Test-Suite (Rundung, leere Warenkörbe, negative Erstattungspositionen, unbekannte Kategorien) läuft gegen jedes Plug-in in dessen eigener Pipeline. Jedes Länderteam ergänzt eigene Fälle aus veröffentlichten Steuerleitfäden.

Der Kern ändert sich nicht mehr, wenn ein Land hinzukommt; der Start in einem vierten Land ist also ein neues Paket plus Tests, keine Änderung am Checkout.

Die schwierigste Entscheidung ist Schritt 2. Eine zu grobe Kategorienliste zwingt Plug-ins zum Raten; eine zu detaillierte koppelt den Vertrag an das Recht eines einzelnen Landes. Rechnen Sie damit, sie zu überarbeiten, und planen Sie dann die Migration auf eine neue Major-Version.

Die Registry weist inkompatible Plug-ins beim Start zurück, sodass eine Versionsabweichung in einer Deployment-Prüfung auffällt und nicht als falsche Steuer auf dem Beleg eines Kunden.

# package: tax_contract  (version 2.1.0)
from dataclasses import dataclass
from decimal import Decimal
from typing import Protocol

CONTRACT_VERSION = (2, 1)

@dataclass(frozen=True)
class TaxLine:
    sku: str
    category: str      # "hot-food", "cold-food", "alcohol", "delivery-fee"
    net: Decimal       # never float for money

@dataclass(frozen=True)
class TaxResult:
    total_tax: Decimal
    breakdown: dict[str, Decimal]   # label shown on the receipt

class TaxRules(Protocol):
    country: str                    # ISO 3166-1 alpha-2, e.g. "DE"
    requires_contract: tuple[int, int]
    def calculate(self, lines: list[TaxLine], region: str | None) -> TaxResult: ...
Das Vertragspaket, versioniert und veröffentlicht getrennt vom Kern und von den Plug-ins.
from importlib.metadata import entry_points
from tax_contract import CONTRACT_VERSION

class UnsupportedCountry(Exception): pass

class TaxRegistry:
    def __init__(self, log):
        self._by_country = {}
        for ep in entry_points(group="fooddelivery.tax_rules"):
            plugin = ep.load()()
            major, minor = plugin.requires_contract
            if major != CONTRACT_VERSION[0] or minor > CONTRACT_VERSION[1]:
                log.warning("skipping %s: needs contract %s", ep.name, plugin.requires_contract)
                continue
            self._by_country[plugin.country] = plugin

    def for_country(self, country):
        if country not in self._by_country:
            raise UnsupportedCountry(country)   # fail loudly, never guess a rate
        return self._by_country[country]
Die Registry im Kern: erkennt installierte Plug-ins, prüft die Vertragskompatibilität und schlägt sie nach Land nach.

Die Steuer-Plug-ins zur Laufzeit

Ausgearbeitetes Beispiel: Der Checkout fragt die Steuer-Registry nach dem Plug-in für das Land des Kunden und ruft es über den Vertrag TaxRules aufCheckoutKern-Use-CaseTaxRegistryLand → Plug-in1 for("DE")2 Plug-inTaxRulesVertrag v2.13 calculate(lines)gb_tax 2.0.4braucht v2.0de_tax 1.3.0braucht v2.1us_tax 3.1.0braucht v1.4übersprungen:falsche MajorLaden: Beim Start liest die Registry installierte Pakete (Entry Points), prüft für jedes Plug-indie benötigte Vertragsversion und registriert es nach Land; inkompatible werden protokolliertund übersprungen, statt den Checkout lahmzulegen.registriert perLändercode
Der Checkout fragt die Registry nach dem für ein Land registrierten Plug-in und ruft es über den Vertrag auf; ein Plug-in, das für eine inkompatible Vertragsversion gebaut ist, wird beim Start übersprungen.

Den Stil im Code durchsetzen

Eine Plug-in-Architektur bleibt nur dann eine, wenn ihre Regeln automatisch geprüft werden. Die Struktur, die in den meisten Sprachen funktioniert, hat drei Arten von Paketen: ein kleines Vertragspaket nur mit Schnittstellen und Datentypen; den Kern, der vom Vertrag abhängt; und ein Paket pro Plug-in, das jeweils vom Vertrag abhängt und von nichts anderem aus Ihrem Code.

Setzen Sie die Abhängigkeitsregeln dann mit Werkzeugen der Art durch, die Abschnitt 3.4 behandelt: In Python kann import-linter dem Kern verbieten, irgendein Plug-in-Paket zu importieren, und Plug-ins verbieten, einander oder den Kern zu importieren; in Java leisten ArchUnit-Tests oder Modulsysteme (JPMS, OSGi) dasselbe. Lassen Sie den Build fehlschlagen, wenn eine Regel verletzt wird.

Ergänzen Sie eine Contract-Test-Suite: eine Reihe von Tests, die einmal gegen den Vertrag geschrieben werden und gegen jedes Plug-in in dessen eigener Pipeline laufen. Sie prüft das Verhalten, das der Vertrag zusagt (Rundung, leere Eingaben, Fehlertypen), nicht das Steuerrecht jedes Landes, das die eigenen Tests des Plug-ins abdecken. Fügen Sie schließlich eine Startprüfung oder einen Smoke-Test beim Deployment hinzu, der jedes installierte Plug-in lädt und seine Vertragsversion verifiziert, damit Inkompatibilitäten auffallen, bevor Nutzer sie finden.

Häufige Fragen

Ist ein Microkernel nur das Strategy-Muster?

Auf Code-Ebene ist das Strategy-Muster derselbe Mechanismus: zur Laufzeit eine Implementierung einer Schnittstelle wählen. Der Microkernel ist die architektonische Variante: Plug-ins werden separat gebaut, versioniert und oft separat installiert, eine Registry erkennt sie, und der Vertrag ist eine veröffentlichte API mit Kompatibilitätsregeln.

Können Plug-ins eigene Daten haben?

Ja, und oft sollten sie das, aber behandeln Sie sie wie die modul-eigenen Daten eines modularen Monolithen: Die Tabellen oder Dateien eines Plug-ins gehören ihm, und der Kern erreicht sie nur über den Vertrag.

Sollte Konfiguration Plug-ins ersetzen?

Für kleine, datenartige Unterschiede (ein Satz, ein Schwellenwert, eine Beschriftung) ja: Konfiguration ist günstiger und sicherer. Plug-ins sind für Unterschiede gedacht, die Logik brauchen. Viele Systeme nutzen beides: ein Plug-in pro Land plus Konfiguration für die aktuellen Sätze jedes Landes.

Prüfen Sie Ihr Verständnis

Welche drei Dinge muss eine Plug-in-Architektur neben dem Kern selbst festlegen?

Die Plug-ins, die Verträge, die sie implementieren, und eine Registry, mit der der Kern Plug-ins erkennen und nachschlagen kann, ohne sie im Code zu benennen.

Warum sollten Plug-ins nicht voneinander abhängen?

Weil sie sich dann nicht mehr unabhängig installieren, entfernen, testen und versionieren lassen, was der Hauptnutzen des Stils ist; gemeinsame Bedürfnisse gehören in den Kern oder den Vertrag.

Warum führt Visual Studio Code Extensions in einem separaten Prozess aus?

Damit eine langsame oder abstürzende Extension die Benutzeroberfläche des Editors nicht einfrieren oder abstürzen lassen kann: Es tauscht etwas Performance und Komplexität gegen Isolation von Tausenden externer Autoren.

Wann ist Konfiguration besser als ein Plug-in?

Wenn der Unterschied zwischen Varianten aus Daten besteht, etwa einem Satz, einem Limit oder einer Beschriftung, statt aus Logik. Konfiguration ist einfacher und sicherer zu ändern.

Weiter · 5.6Hexagonal und CleanPorts and Adapters, Onion- und Clean Architecture: die Geschäftslogik im Zentrum, Abhängigkeiten zeigen nach innen, treibende und getriebene Ports, ein Python-Beispiel mit einem echten Adapter und einem Test Double sowie die Kosten.Weiter →

Quellen und weiterführende Literatur

Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.

Fragen: „Wir verkaufen eine Außendienst-App an 40 Kunden, und jeder möchte eigene Regeln für die Einsatzplanung. Sollten wir eine Plug-in-Architektur bauen, oder reicht Konfiguration?“