Anwendungen strukturiert aufbauen
Seite 2: Beispiele fĂĽr Lego-Denken
In der Praxis gibt es zahlreiche konkrete Beispiele, die die zuvor genannten Entscheidungskriterien demonstrieren. Interessanterweise sind viele von ihnen älter als der Begriff der Microservices, was lediglich zeigt, dass die zugrunde liegenden Ideen und Konzepte wesentlich älter sind, als es der Hype vermuten lässt.
Eins der Beispiele sind die zahlreichen Werkzeuge, die unixoide Betriebssysteme auf der Kommandozeile zur Verfügung stellen. Die Kommandos laufen als eigenständige Prozesse, erfüllen genau eine Aufgabe und lassen sich flexibel kombinieren. Genau das zeichnet die Mächtigkeit der Kommandozeile im Gegensatz zu den meisten grafischen Benutzeroberflächen aus. Ein aktuellere Umsetzung sind unterschiedliche Anwendungen des Toolanbieters HashiCorp, die sich zum Verwalten einer Cloud-Infrastruktur verwenden lassen. Sie erfüllen eine abgesteckte Aufgabe und überlappen sich fachlich nicht. Theoretisch könnte man alle Tools in eine einzige Applikation zusammenpacken. Allerdings ginge so die Flexibilität verloren und man hätte einen riesigen Monolithen, den man entweder ganz oder gar nicht verwenden kann.
Wie hängen Dinge zusammen?
Der Zusammenhang der Elemente ist entscheidend. Als Beispiel sei ein Cloud-Dienst gewählt, auf den sich Dateien hoch- und später von dort wieder herunterladen lassen. Auf den ersten Blick scheint das eine simple Webanwendung zu sein, doch stellen sich rasch zahlreiche Fragen, die das Projekt nicht mehr ganz so einfach erscheinen lassen:
- Wäre es sinnvoll, die Web-UI von der Implementierung des Funktionsumfangs zu trennen? Sollte es folglich eine REST-API für die Funktionen geben, die die UI im Hintergrund verwendet?
- Ist das Backend, das die hochgeladenen Dateien speichert, austauschbar zu gestalten, um beispielsweise zwischen einem lokalen Datenträger und einem anderen Dienst wechseln zu können?
- Wie steht es damit, die Authentifizierung auszulagern und beispielsweise eine mit Tokens arbeitende Anmeldung zu verwenden?
- Bietet es sich an, das Logging auszulagern, und beispielsweise einen gesonderten Logserver einzusetzen, der entsprechende Nachrichten einsammelt und verarbeitet?
Alle Fragen lassen sich mit ja beantworten, wenn man Wert auf kleine, isolierte Einheiten legt, die unabhängig voneinander funktionieren. Selbst ein derart kleines Beispiel lässt sich in unterschiedliche Dienste zerlegen, da viele der genannten Aspekte (Authentifizierung, Logging, Persistenz) gar nicht mit der primären Fachlichkeit zusammenhängen, sondern sich in zahlreichen anderen Anwendungen ebenfalls nutzen ließen.
Eine solche Aufteilung führt zudem dazu, dass die einzelnen Komponenten besser und einfacher testbar sind und unterschiedliche Techniken zur Implementierung dienen können. Damit lässt sich verhindern, dass eine einzelne Technik systembeherrschend wird, weil man einzelne Dienste leicht austauschen kann, sofern die Schnittstelle nach außen gleich bleibt.
Das wiederum wirft die Frage nach einem technikunabhängigen Kommunikationsprotokoll auf. In dem Fall bietet sich HTTP an, über das sich offene Formate wie JSON oder XML austauschen lassen. Ausgeschlossen sind hingegen proprietäre Lösungen, sei es auf Protokoll- oder auf Formatebene.
Fachliche Strukturen schaffen
Nicht genug betont werden kann dabei der Aspekt, dass fachliche Strukturen zu schaffen sind und keine technischen. Ein klassisches Gegenbeispiel ist die übliche Struktur einer Anwendung, die auf dem MVC-Entwurfsmuster basiert. Häufig gibt es dort drei Verzeichnisse namens controller, model und views. Die Gliederung hat sich inzwischen so eingebürgert, dass kaum jemand sie noch hinterfragt – dabei ist sie ein Paradebeispiel für schlechtes Design.
Niemand arbeitet zunächst an sämtlichen Controllern und erst danach an den Views. Stattdessen findet die Arbeit stets übergreifend statt, etwa wenn beide Elemente für den Anmeldedialog zu implementieren sind. Fachlich zusammengehörende Komponenten wie sie trennt die beschriebene Struktur allerdings.
In dem Fall handelt es sich folglich nicht um eine Separation of Concerns, sondern eine Separation of Object Types. In der Praxis ist solch ein Vorgehen in etwa so sinnvoll wie das Gliedern eines Projekts nach classes, interfaces, structs und enums. Den Anbietern von MVC-Frameworks bleibt jedoch nichts anderes übrig, da sie nichts über den Anwendungsfall des Entwicklers wissen, der das Framework verwendet. In Ermangelung dessen geben sie daher die technische als ihnen einzig mögliche Struktur vor.
Für mit cross-cutting bezeichnete technische Belange einer Anwendung ist eine gesonderte Struktur zu schaffen. In der Regel handelt es sich dabei allerdings nur um Hilfsbausteine, die die eigentliche Aufgabe der Software unterstützen. Es gilt daher, nicht die Ausnahme zur Regel zu erklären.
Als häufiges Gegenargument führen Fürsprecher die technische Struktur großer Frameworks wie .NET ins Feld. Bei einem Blick auf sie lässt sich tatsächlich eine entsprechende Hierarchie erkennen (siehe Abbildung). Die Wurzel aller Namensräume trägt die Bezeichnung System, darunter liegen Räume wie System.Configuration, System.Data und System.Deployment.
Bereits der Name erklärt jedoch die Zuständigkeit der Namensräume: Sie behandeln technische und nicht fachliche Aspekte des Codes. Im Kontext von .NET ist das genau richtig, da es sich dabei gerade nicht um ein Anwendungsframework handelt, das auf eine bestimmte Fachdomäne spezialisiert ist, sondern ein Basisframework, dessen Fachlichkeit die Technik ist. Daher ist die von Microsoft gewählte Struktur durchaus richtig und angemessen.