Softwarearchitektur verstehen und entscheiden

Aaron KreisÜber mich
Anwendungsarchitektur

Inversion of Control

Das Framework ruft den eigenen Code auf

Die Idee und ihr Nutzen

Ein Team einer Versicherung schreibt für die Schadenanwendung eine Funktion, die eine Schadenakte lädt, wenn ein Sachbearbeiter die Akte im Browser aufruft. Wie Webanfragen empfangen, gelesen und beantwortet werden, muss das Team nicht selbst programmieren. Das erledigt ein Framework, ein fertiges Programmgerüst, das auf Anfragen wartet und bei jeder Anfrage nach einer Schadenakte die Funktion des Teams aufruft. So kann sich das Team auf das Laden von Schadenakten und die Prüfung des Leserechts konzentrieren. Üblicherweise ruft der Code eines Teams fertigen fremden Code auf, wenn er ihn braucht. Hier ruft umgekehrt das fremde Framework die Funktion des Teams auf, sobald ein Sachbearbeiter eine Schadenakte öffnet. Diese Umkehrung heißt Inversion of Control, auf Deutsch Umkehrung der Steuerung.

Bausteine: Sachbearbeiter: öffnet eine Schadenakte im Browser; Framework: empfängt Anfragen, sendet Antworten; zeigeSchaden (Team-Code): prüft das Leserecht, lädt die Akte; Schadendatenbank: enthält die Schadenakte. Verbindungen: Sachbearbeiter → Framework; Framework → zeigeSchaden (Team-Code) (ruft auf); zeigeSchaden (Team-Code) → Schadendatenbank. Nicht der Code des Teams steuert den Ablauf, sondern das Framework ruft den Code des Teams auf.

Wie funktioniert es?

Das Team meldet seine Funktion zeigeSchaden beim Framework für Adressen der Form /schaeden/{nummer} an, also für jede Adresse, die mit /schaeden/ beginnt und mit einer Schadennummer endet. Aufgerufen wird die Funktion später vom Framework. Beim Start der Schadenanwendung übernimmt das Framework die Steuerung und wartet auf eingehende Anfragen. Ein Sachbearbeiter öffnet im Browser die Adresse /schaeden/4711. Das Framework empfängt die Anfrage und erkennt anhand der angemeldeten Adressen, dass zeigeSchaden zuständig ist. Dann liest das Framework die Nummer 4711 aus der Adresse und ruft zeigeSchaden(4711) auf. Das Team hat dafür eine eigene Regel geschrieben. Mit dieser Regel prüft die Funktion zeigeSchaden zuerst, ob der Sachbearbeiter Schaden 4711 sehen darf. Der Sachbearbeiter darf die Akte sehen, also lädt die Funktion die Schadenakte aus der Schadendatenbank. Die Schadendatenbank liefert die Akte, und die Funktion gibt nur die Daten der Akte an das Framework zurück, keine fertige Antwort für den Browser. Das Framework macht daraus die Antwort und schickt sie an den Browser.

Ein Framework unterscheidet sich von einer Bibliothek, einer Sammlung fertiger Hilfsfunktionen, durch die Richtung der Aufrufe: Der Code des Teams ruft eine Bibliothek auf, wenn er sie braucht. Ein Framework dagegen ruft den Code des Teams auf, sobald sein Ablauf es vorsieht. Das Framework stammt nicht vom Team, sondern von einem Hersteller oder einem freien Projekt, und viele Anwendungen nutzen es in gleicher Form. Das Team kann das Verhalten des Frameworks deshalb nur über dessen vorgesehene Einstellungen und über die angemeldeten Funktionen beeinflussen. Dieselbe Umkehrung gibt es auch bei Bedienoberflächen und automatischen Tests. Ein Framework für Bedienoberflächen wartet auf Eingaben und ruft beim Klick auf „Speichern“ die dafür angemeldete Funktion auf. Bei automatischen Tests sucht ein Testwerkzeug die Testfunktionen des Teams, ruft sie nacheinander auf und sammelt die Ergebnisse.

Hätte das Team die Berechtigungsprüfung vergessen, würde das Framework die Akte trotzdem ausliefern. Das Framework erledigt nur die technischen Aufgaben, für die es gebaut ist. Fachliche Regeln wie die Berechtigungsprüfung muss das Team selbst programmieren oder im Framework ausdrücklich einrichten. Scheitert die Funktion mit einem Fehler, fängt das Framework den Fehler ab und schickt eine Fehlerantwort an den Browser. Welche Fehlerantwort das ist, legt das Team über die Einstellungen des Frameworks fest. Ist für eine Adresse keine Funktion angemeldet, antwortet das Framework, dass es die Seite nicht gibt.

Inversion of Control: das Framework ruft die Funktion des Teams auf

Beteiligte: Sachbearbeiter: öffnet /schaeden/4711 im Browser; Framework: empfängt Anfragen, wählt die angemeldete Funktion, sendet die Antwort; zeigeSchaden (Team-Code): prüft das Leserecht, lädt die Akte; Schadendatenbank: enthält die Schadenakte. Ablauf: 1. Das Team meldet seine Funktion zeigeSchaden beim Framework für Adressen der Form /schaeden/{nummer} an. Aufgerufen wird die Funktion später vom Framework. 2. Ein Sachbearbeiter öffnet im Browser die Adresse /schaeden/4711. 3. Das Framework erkennt die zuständige Funktion, liest 4711 aus der Adresse und ruft zeigeSchaden(4711) auf. 4. Die Funktion zeigeSchaden prüft mit einer selbst geschriebenen Regel, ob der Sachbearbeiter Schaden 4711 sehen darf. Hätte das Team diese Prüfung vergessen, würde das Framework die Akte trotzdem ausliefern. 5. Der Sachbearbeiter darf die Akte sehen; die Funktion zeigeSchaden lädt die Akte aus der Schadendatenbank. 6. Die Schadendatenbank liefert die Akte an die Funktion zeigeSchaden. 7. Die Funktion zeigeSchaden gibt nur die Daten an das Framework zurück. 8. Das Framework schickt die Antwort an den Browser. Empfang, Lesen der Adresse und Versand der Antwort erledigt das Framework, nicht der Code des Teams.

Wann ist es sinnvoll und wann nicht?

Inversion of Control lohnt sich

  • Viele Anwendungen brauchen denselben Ablauf. Das gilt für Webanfragen und automatische Tests, aber auch für nächtliche Stapelläufe, die große Datenmengen ohne Bediener verarbeiten. Ohne Framework programmiert jede Anwendung diesen Ablauf in einer eigenen Fassung, und ein Fehler darin muss in jeder Fassung einzeln gefunden werden. Mit einem Framework ist dieser Ablauf in allen Anwendungen gleich und vielfach erprobt.
  • Ein Team will sich auf seine fachlichen Aufgaben konzentrieren. Steuert eine Webanwendung ihren Ablauf selbst, muss sie auf eingehende Verbindungen warten, jede Anfrage lesen, aus der Adresse die zuständige Funktion ermitteln, Fehler abfangen und die Antwort im richtigen Format zurückschicken. Programmiert jedes Team das selbst, entsteht viel doppelter und fehleranfälliger Code, und die wenigen Zeilen zum Laden einer Schadenakte stehen zwischen vielen Zeilen für Empfang und Versand. Mit dem Framework stehen das Laden der Schadenakte und die Berechtigungsprüfung übersichtlich in einer Funktion.
  • Die Anwendung braucht einen ganzen Ablauf vom Empfang einer Anfrage bis zum Versand der Antwort und nicht nur einzelne Hilfsfunktionen. Mit einer Bibliothek allein müsste das Team diesen Ablauf selbst programmieren.
  • Mehrere Teams sollen ihre Funktionen auf dieselbe Weise in eine Anwendung einhängen. Ein zweites Team baut zum Beispiel eine Seite für die Dokumente zu einem Schaden. Das zweite Team meldet dafür nur eine weitere Funktion beim Framework an, so wie das Team, das zeigeSchaden geschrieben hat. Empfang von Anfragen und Versand von Antworten laufen für beide Funktionen gleich ab. Ohne Framework müsste das zweite Team erst im Code des anderen Teams nachlesen, wie die Schadenanwendung Anfragen empfängt und an die zuständige Funktion weitergibt.

Inversion of Control ist nicht das Richtige

  • Der Ablauf passt nicht zu den Vorgaben eines Frameworks. Ein Beispiel ist ein Programm, das eine Netzverbindung stundenlang offen hält und laufend Daten schickt, während ein Framework für Webanfragen zu jeder Anfrage genau eine Antwort vorsieht. Mit einem Framework müsste das Team diese Vorgaben ständig mit eigenem Code umgehen. Stattdessen steuert das Team den Ablauf selbst und nutzt für einzelne Schritte Bibliotheken. Empfang und Versand programmiert das Team dann selbst, und der Code zum Umgehen der Vorgaben entfällt.
  • Das Programm ist ein kurzes Skript, das seine Schritte einmal von oben nach unten abarbeitet. Ein Beispiel ist eine einmalige Umwandlung von Dateien. Es gibt dann keine Anfragen, auf die ein Framework warten und zu denen es Funktionen des Teams aufrufen könnte. Stattdessen schreibt das Team die Schritte nacheinander in das Skript. Das Team muss weder ein Framework einbinden noch dessen Regeln für das Anmelden von Funktionen lernen.
  • Das Team muss jeden Schritt des Ablaufs genau kennen und selbst bestimmen. Ein Grund können strenge Vorgaben an die Antwortzeit sein. Ein Framework verdeckt solche Schritte. Stattdessen programmiert das Team den Ablauf mit Empfang, Fehlerbehandlung und Versand selbst, und jeder Schritt steht im eigenen Code, wo das Team ihn messen und ändern kann.
  • Die Anwendung braucht vom fremden Code nur einzelne Hilfsfunktionen und keinen fertigen Ablauf. Beispiele sind Hilfsfunktionen zum Umrechnen von Datumsangaben oder zum Erzeugen von PDF-Dateien. Ein Framework brächte dann einen ganzen Ablauf mit, den die Anwendung nicht braucht. Stattdessen verwendet das Team eine Bibliothek, und der Code des Teams ruft die Hilfsfunktionen auf, wenn er sie braucht. Das Team muss sich so nicht nach den Vorgaben eines Frameworks richten und dessen Regeln nicht lernen.
  • Nur das Erzeugen der Helfer soll umgedreht werden, nicht der ganze Ablauf. Helfer sind andere Programmteile, die ein Programmteil braucht. Dann setzt das Team Dependency Injection ein: Der Programmteil erzeugt seine Helfer nicht selbst, sondern bekommt sie beim Start von außen übergeben. Den übrigen Ablauf steuert der Code des Teams weiter selbst, ein Framework ist dafür nicht nötig.

Nachteile und typische Fehler

Der Ablauf ist nicht mehr im eigenen Code sichtbar. Wer das Framework nicht kennt, sieht dem Code des Teams nicht an, wann und warum zeigeSchaden aufgerufen wird. Neue Teammitglieder müssen deshalb zuerst die Regeln des Frameworks lernen. Dazu gehört, wie Adressen angemeldet werden und in welcher Reihenfolge das Framework die Funktionen des Teams aufruft. Die Anwendung bindet sich an das Framework. Ein Wechsel zu einem anderen Framework ist aufwendig, weil jede angemeldete Funktion auf die Vorgaben des bisherigen Frameworks zugeschnitten ist. Das Team muss außerdem neue Versionen des Frameworks einspielen, die zum Beispiel Sicherheitslücken schließen. Dabei muss das Team prüfen, ob sich das Verhalten beim Aufruf seiner Funktionen ändert.

Typische Fehler und woran man sie erkennt:

  • Vergessene Berechtigungsprüfung: Ein Entwickler des Teams baut zeigeSchaden ohne Berechtigungsprüfung, weil er annimmt, das Framework prüfe die Berechtigungen. Das Framework ruft zeigeSchaden aber bei jeder passenden Adresse auf, ohne das Leserecht zu prüfen. Jeder Sachbearbeiter erhält so jede Schadenakte. Erkennbar ist das erst, wenn ein Test oder eine Prüfung gezielt Akten mit einem Benutzer ohne Leserecht abruft.
  • Fehler erst im Betrieb: Der Code des Teams ruft das Framework nicht selbst auf, sondern das Framework ruft den Code des Teams beim Start oder bei einer Anfrage auf. Ein Fehler im Zusammenspiel, etwa eine fehlende Einstellung, fällt deshalb erst auf, wenn das Framework die Anwendung startet oder die betroffene Funktion zum ersten Mal aufruft. Die Fehlermeldungen des Frameworks sind dann oft lang und schwer zu lesen. Erkennbar ist das an Fehlern, die erst beim Start der Anwendung oder beim ersten Aufruf einer Adresse auftreten.
  • Nicht angemeldete Funktion: Das Team schreibt eine Funktion für die Dokumente zu einem Schaden, meldet sie aber für /schaden/{nummer}/dokumente statt für /schaeden/{nummer}/dokumente an. Ein Sachbearbeiter öffnet /schaeden/4711/dokumente. Das Framework findet für diese Adresse keine angemeldete Funktion und antwortet, dass es die Seite nicht gibt. Erkennbar ist das an Fehlerantworten für Adressen, die es eigentlich geben müsste.
  • Arbeit gegen das Framework: Das Team umgeht die Vorgaben des Frameworks mit eigenem Code. Ein Beispiel ist ein selbst gebauter Empfang einzelner Anfragen. Erkennbar ist das an viel Code, der Aufgaben doppelt erledigt, die das Framework schon übernimmt.

Weiterführend

Verwandte Ansätze

  • Plugin Architecture: Bauweise, bei der eine Anwendung angemeldete Erweiterungen bei festgelegten Anlässen aufruft.
  • Microkernel Architecture: Bauweise mit einem kleinen Kernprogramm, das Erweiterungen über eine feste Schnittstelle aufruft.
  • Model View Controller: Aufteilung einer Oberfläche in Daten, Anzeige und Steuerung. Frameworks für Webanwendungen rufen dabei die Steuerungsteile des Teams auf.

Werkzeuge und Umsetzungen

Fast jedes Framework beruht auf diesem Prinzip. Bekannte Beispiele:

  • Spring Framework: Java-Programmgerüst, das Webanfragen, Transaktionen und das Erzeugen von Programmteilen steuert und dabei eigenen Code aufruft.
  • Django: Python-Programmgerüst für Webanwendungen, das für jede Adresse die angemeldete Funktion aufruft.
  • ASP.NET Core: Programmgerüst der Plattform .NET für Webanwendungen.
  • JUnit: Testwerkzeug für Java, das die Testfunktionen eines Teams aufruft und die Ergebnisse sammelt.

Quellen