This article is also available in English
Martin Fowler hat Software-Architektur als die Entscheidungen definiert, die wichtig und gleichzeitig schwer änderbar sind (Youtube-Video). Daraus könnte man ableiten, dass man eine ideale, zukunftssichere Software-Architektur niemals ändern muss, denn dann unterbleiben die schwierigen Änderungen ja. Das hat Martin Fowler aber eher nicht beabsichtig. Dieser Artikel beschreibt, warum eine in diesem Sinne zukunftssichere Architektur nicht nur unerreichbar ist, sondern auch kein sinnvolles Ziel darstellt.
Eine Geschichte
Nehmen wir ein Beispiel: In dem Projekt wird beschlossen, das System Modul für Modul in eine neue Architektur zu migrieren. Das Vorgehen wird mit Hilfe eines Prototyps technisch validiert. Das Vorgehen ist nachvollziehbar: Eine schrittweise Migration ist eigentlich immer sinnvoll, und ein technisches Risiko mit einem Prototyp beherrschbar zu machen, ist bei größeren Risiken eine gute Idee.
Nach dem Start des Projekts lernt das Team mehr über die Fachlichkeit, die Anforderungen und das existierende System. Dabei stellt sich heraus, dass eine Migration Modul für Modul es unmöglich macht, die gewünschten neuen fachlichen Unterstützungen für die Benutzer:innen umzusetzen. Zusätzlich gibt es noch einige andere Probleme, so dass das Projekt gestoppt wird und als Fehlschlag bewertet wird.
Lesson Learned?
Wie bei jedem Fehlschlag sollte man sich die Frage stellen, wie man in Zukunft so ein Ergebnis vermeiden kann. Eine mögliche Konsequenz ist, dass Projekte in Zukunft noch mehr Absicherungen vorab treffen. Man kann die Voraussetzungen für die Genehmigung von Projekten entsprechend verscahärfen. Der implementierte Prototyp geht schon in die Richtung. Konkret würde die Architektur in dem Beispiel vor dem Projekt-Start eine Migration definieren müssen, die die gewünschte Unterstützung der Benutzer:innen ermöglicht, so dass die Architektur nicht so schnell umgeworfen werden muss.
Meiner Meinung nach ist diese Konsequenz falsch – so logisch sie auch klingen mag. Die richtige Reaktion ist, die Architektur immer dann zu überarbeiten, wenn neue Fakten bekannt geworden sind. Dafür gibt es mehrere Gründe:
Software bildet ein Modell der Domäne. Geschäftsregeln, Geschäftsobjekte und andere Elemente der Domäne werden in Software abgebildet. Diese Abbildung ist niemals perfekt. Entwickler:innen lernen ständig neue Fakten über die Domäne. Sie müssen die Modelle und damit auch die Software selbst anpassen. Dieser Lernprozess ist unausweichlich: Man kann niemandem befehlen, die Domäne sofort vollständig zu verstehen. Das erzwingt ein iteratives Vorgehen, bei der Entwickler:innen die Software ständig an das Gelernte anpassen.
Anforderungen ändern sich. Solche Änderungen können sich durch den Wandel im Geschäft ergeben. Benutzer:innen können bei der Arbeit mit der Software und der Reflektion darüber auch neue Ideen bekommen, wie die Software die Arbeitsprozesse noch besser unterstützen kann.
Teams lernen, wie man Architekturen entwirft. Ich habe dazu eine Umfrage auf Twitter und Mastodon erstellt. Die breite Mehrheit der Teilnehmer:innen hat an weniger als zehn Architekturen mitgearbeitet. Die Umfrage ist zwar nicht repräsentativ, aber ähnliche Umfragen beispielsweise bei der Einteilung von Gruppen in Trainings ergeben eine noch geringere Anzahl an Architekturen, an denen die Personen mitgearbeitet haben. Das ist auch so zu erwarten: Schließlich arbeiten Menschen typischerweise längere Zeit – beispielsweise mehrere Jahre – an einem Projekt mit einer Architektur. Logischerweise können sie also nicht an der Erstellung allzu vieler Architekturen beteiligt sein.
Aber selbst, wenn man ständig an Architekturen arbeitet: Die gesamte Branche lernt ständig über Architekturen hinzu. Das sollte nicht überraschend sein. Immerhin ist Software-Architektur alles andere als ein einfaches Thema. Sich mit neuen Software-Architektur-Ansätzen zu beschäftigen, ist ein wichtiger Teil unserer Tätigkeit: Um Ideen und Erfahrungen kennen zu lernen, schreiben und lesen wir Artikel, gehen auf Konferenzen oder tauschen uns anderweitig aus. Das bedeutet aber auch, dass ein Entwurf für eine Software-Architektur, die wir jetzt erstellen, in Zukunft anders aussehen wird und wir gegebenenfalls sogar eine bereits existierende Architektur anpassen müssen.
Also lernen wir die Domäne besser kennen, Anforderungen ändern sich und wir lernen, wie man bessere Architekturen entwirft. Die Architektur wird daher verbesserungswürdig sein – und zwar immer, nicht nur zu Beginn. Es ist sogar so, dass komplexe Systeme nur entworfen werden können, indem man zunächst ein einfaches System entwirft und es dann weiterentwickelt. Am Anfang gleich eine Architektur für ein komplexes System zu entwerfen, muss also scheitern.
Beim Beispiel-Projekt würde also eine bessere Absicherung zu Beginn nicht viel nützen: Vielleicht würde man beim nächsten Mal vor Projekt-Beginn mehr Hürden identifizieren und eliminieren. Aber dann lernt man im Projekt eben etwas anderes, das in der aktuellen Architektur nicht abgebildet war, und muss die Architektur dann aus diesem Grund fundamental ändern. Es könnte sogar besser sein, weniger Absicherungen vorab zu treffen: Das Projekt hat einen Prototyp gebaut, um Modul-für-Modul-Migrationsansatz zu validieren. Der wurde später wegen der Fachlichkeit verworfen. Den Prototypen hätte man sich also sparen können.
Also sollte die Lesson Learned eine andere sein: Schlechte Software-Architekturen können wir durch mehr Absicherung vorab kaum vermeiden. Wir müssen auf Iterationen bei der Entwicklung der Architektur setzen.
Wieviel Architektur benötigen wir?
Das führt zu der Frage, wie viel Architektur zu Beginn eines Projekts überhaupt festgelegt werden muss. Tobias Goeschel nutzt dazu einen sehr radikalen Ansatz, den er „Domain Prototyping“ nennt (Folge 134 bei Software-Architektur im Stream). Das Projekt startet mit einem möglichst kleinen Technologie-Stack. Typischerweise nutzen Projekte heutzutage eine Datenbank und einen Web-Server. Domain Prototyping verzichtet zunächst sogar auf diese Technologien – und natürlich auch auf Messaging-Lösungen wie Kafka oder andere, fortgeschrittene Technologien. Also entsteht ein System, das als komplette Anwendung auf nur einem Rechner läuft und einen rudimentären Technologie-Stack nutzt. Damit können sich Entwickler:innen nur um die Geschäftslogik und ihre Strukturierung kümmern. Änderungen an der Geschäftslogik sind sehr einfach umsetzbar, weil kein Technologie-Code angepasst werden muss. So können Entwickler:innen sich nicht nur auf die Geschäftslogik konzentrieren, sondern können notwendige Änderungen an der Logik auch sehr einfach durchführen. Die entstehende Architektur strukturiert dann die Geschäftslogik. Das ist ein wichtiger Aspekt der Software-Architektur, weil so fachliche Änderungen besonders gut unterstützt werden, und wichtiger als viele technische Aspekte, die sonst oft in einer Architektur betrachtet werden.
Technologien wie Datenbanken werden erst dann eingeführt, wenn sie notwendig sind. Dann haben die Teams schon Erfahrungen mit der Geschäftslogik gesammelt. Das unterstützt die Auswahl der geeigneten Technologie: Es gibt beispielsweise sehr viele unterschiedliche NoSQL-Datenbanken und natürlich auch relationale Datenbanken. Sie haben unterschiedliche Eigenschaften. Wenn man besser versteht, was man umsetzen muss, kann die Technologie auswählen, deren Eigenschaften für die Geschäftslogik besonders vorteilhaft sind. In dem Beispiel-Projekt hätte man bei diesem Vorgehen vermutlich erst die Migrationsmethode gewählt, wenn man schon einige Erfahrungen mit der Geschäftslogik gesammelt hat. Dadurch hätte man dann eher einen geeigneten Migrationsansatz gewählt.
Last Responsible Moment
Domain Prototyping ist ein sehr radikaler Ansatz. Aber in jedem Projekt kann man bei jeder Frage überlegen, wann der letzte Zeitpunkt ist, zu dem eine Entscheidung noch verantwortungsvoll treffen kann („Last Responsible Moment“, letzter verantwortbarer Moment). Das ist die Verallgemeinerung der Frage, wie viel man am Anfang eines Projekts entscheiden muss. Generell hat eine spätere Entscheidung den Vorteil, dass mehr Fakten bekannt sind, und dadurch ist die Entscheidung besser. Das Konzept hat allerdings ein Problem: Es ist subjektiv und hängt vom Mut der Beteiligten ab. So ist nicht klar, ob es für das Beispiel-Projekt einen Unterschied gemacht hätte. Dort wird die Entscheidung für die Migrationsstrategie mit einem Prototyp abgesichert, was eher für wenig Mut spricht. Es erscheint nicht sonderlich wahrscheinlich, dass man in einem solchen Umfeld den Mut aufgebracht hätte, die Entscheidung für eine Migrationsstrategie so lange aufzuschieben, bis die neuen Erkenntnisse über die Domäne zur Auswahl einer anderen Strategie führen.
YAGNI
Ein weiteres Konzept in diesem Bereich ist YAGNI („You Ain’t Gonna Need It“, Du wirst es nicht benötigen). Dieses Prinzip des Extreme Programmings (XP) besagt, dass man eine Funktionalität erst dann in einem System einbauen sollte, wenn sie wirklich notwendig ist. YAGNI ist eine Art Gehirnwäsche gegen BDUF („Big Design Upfront“, großes Design am Anfang). Weil man eben zukünftige Änderungen nicht vorhersagen kann, ist ein BDUF keine gute Idee. Damit scheint YAGNI genau in die bereits diskutierte Kerbe zu schlagen.
YAGNI ist aber manchmal zu radikal. Es kann dazu führen, dass Informationen ignoriert werden. Wenn man eine potenzielle Anforderung kennt, kann man sie durchaus betrachten und sich Gedanken über den Impact einer möglichen Implementierung dieser Anforderung machen. Ob man dann diese Anforderung bei der aktuellen Architektur tatsächlich schon berücksichtigt, ist eine andere Frage.
Aber wie kann dann ein weniger radikales Vorgehen aussehen?
Den Gipfel im Blick
Eine Analogie ist eine Expedition auf den Gipfel eines Berges. Das Erreichen des Gipfels steht für das erfolgreiche Beenden des Projekts. Bei der Architektur scheint oft der Fokus genau auf der Zielarchitektur für den Endzustand des Projekts zu liegen. Das ist nachvollziehbar: Schließlich muss die Architektur alle diese Probleme lösen. Auch bei einer Expedition ist es sinnvoll aufzublicken, um zu schauen, ob man noch auf dem Weg zum Gipfel ist. Aber bei einer Expedition ist es auch wichtig – vielleicht sogar wichtiger – den jeweils nächsten Schritt zu machen und das nächste Hindernis – beispielsweise einen Fluss – zu überqueren. So sollte es bei der Architektur auch sein: Die Architektur muss es in erster Linie ermöglichen, die aktuellen Herausforderungen wie zum Beispiel neu zu implementierende Features unterstützen. Dabei darf man die Zielarchitektur nicht aus den Augen verlieren – aber sie ist sekundär und langfristig relevant.
Als neues Akronym bietet sich vielleicht DBS an („Don’t Be Stupid“, sei nicht dumm). Änderungen, die sehr wahrscheinlich passieren, sollte schon beim Architektur-Entwurf betrachten. Aber der Fokus sollte auf den aktuell zu implementierenden Features und Zielen liegen, nicht so sehr auf einer „perfekten“, scheinbar zukunftssicheren Zielarchitektur. Schließlich muss diese eh nachjustiert werden und aktuellen Erkenntnissen angepasst werden. Dafür sind die Erkenntnisse aus den aktuellen Herausforderungen notwendig.
Zukunftssicher? Lieber nicht
Wir haben es oft mit suboptimalen Architekturen zu tun. Eine wichtige Hypothese dieses Artikels ist, dass diese Architekturen nicht entstanden sind, weil der erste Architektur-Entwurf suboptimal war, sondern weil die Architektur nicht angepasst worden ist.
Eine zukunftssichere Architektur erfordert viel Nachdenken und Aufwand und zielt auf Zukunftssicherheit ab. Wenn etwas implementiert werden soll, wird man bei einer „zukunftssicheren“ Architektur vermutlich versuchen, ohne Änderungen an der Architektur auszukommen. Sonst ist die Architektur offensichtlich nicht zukunftssicher. Damit verführt die „zukunftssichere“ Architektur dazu, auf eigentlich notwendige Anpassungen zu verzichten.
Wenn man hingegen mit dem Paradigma einer iterativen Architektur-Entwicklung an das Projekt geht, wird man sich bei jeder Änderung fragen, ob die Architektur noch trägt und sie gegebenenfalls anpassen oder komplett umstoßen. Durch das fehlende Bias zum Erhalten der Architektur wird sich so am Ende eine bessere Architektur ergeben.
Ebenso kann es sein, dass ein Prototyp wie im Beispiel-Projekt ebenfalls dazu führt, dass man eher an einer Architektur festhält: Schließlich stellt ein Prototyp ein großes finanzielles und emotionales Investment in die Architektur dar. Es ist dann besonders schwierig zuzugeben, dass die Architektur nicht mehr trägt und geändert werden muss.
Das führt zu einer Paradoxie: Der Fokus auf eine „zukunftssichere“ Architektur führt gerade dazu, dass die Architektur nicht zukunftssicher ist. Es ist im Kern ein psychologisches Argument: An einer möglichst „zukunftssicheren“ Architektur zu arbeiten führt dazu, dass man eigentlich notwendige Anpassungen verschleppt, weil man zu viel in die „zukunftssichere“ Architektur investiert hat.
Keine Daumenregeln!
Der Fokus auf eine zukunftssichere Architektur ist nicht der einzige problematische Bias. YAGNI ist ein weiteres Beispiel. Auch Daumenregeln, beispielsweise ob ein Modul eher klein oder groß sein soll, können kontraproduktiv sein: Architektur-Entscheidungen sollten immer begründet werden. Daumenregeln können dazu dienen, Diskussionen abzukürzen und allgemeine Daumenregeln unreflektiert zur Begründung einer Entscheidung zu nutzen.
Außerdem muss man Architektur-Entscheidungen revidieren, falls sich Änderungen ergeben haben, durch welche die ursprüngliche Begründung nicht mehr zutrifft. Eine Daumenregeln macht die konkrete Begründung aber unklar und damit auch den Zeitpunkt, zu dem die Entscheidung revidiert werden soll.
Ergebnis
Als Ergebnis kann man eine Architektur erwarten, die durch einige historische Fehler belastet ist und die aktuellen Anforderungen möglichst gut unterstützt. Vermutlich wird sie nicht ästhetisch ansprechend sein, aber das ist eben auch kein Kriterium, das für die Software-Architektur relevant. Sie soll „nur“ die Geschäftsfunktionalität möglichst gut unterstützen.
Struktur
Die Struktur eines Systems ist ein wichtiger Bestandteil der Architektur, vielleicht sogar der zentrale Teil. Nicht selten geht es dabei um eine Struktur wie in Abb. 1. Die Frage ist nun, ob diese Struktur gut oder schlecht ist – und ob sie zukunftssicher ist. Das Problem der Zukunftssicherheit ist, dass zukünftige Änderungen nicht vorhersagbar sind. Vermutlich werden auch Änderungen an der Struktur notwendig sein. Also scheint es unmöglich, die Struktur zukunftssicher zu gestalten.
Schön wäre es sicher, wenn die Struktur einfach und einfach zu verstehen wäre. Dann ist sie einfach zu ändern, weil eine Änderung ein Verständnis der Struktur voraussetzt. Aber eine echte Bewertung ist erst nach einigen Änderungen möglich. Selbst, wenn man für eine Struktur entscheiden kann, ob sie leicht änderbar ist: Wie kann man die Struktur so entwerfen, dass sie einfach änderbar und verstehbar ist?
Domain-driven Design (DDD) ist ein Ansatz, bei dem die Domäne das Design treibt und dementsprechend die Architektur die Domäne widerspiegeln soll. Damit ist die Struktur auf die jeweilige Fachlichkeit abgestimmt. Sie ist also gerade nicht generisch und hat auch keine unnötige Flexibilität. Generik und Flexibilität sind sonst oft Ansätze, mit denen man versucht, eine Architektur zukunftssicher zu machen.
Damit können wir die Struktur aus Abb. 1 bewerten. Es ist nicht erkennbar, für welche Domäne die Struktur ist. Man könnte sie für die Serverteile eines selbstfahrenden Autos oder das Backend eines Computerspiels nutzen. Sie ist rein technisch und stellt damit sicher einen wichtigen Aspekt dar. Oft wird aber nur eine solche technische Struktur unter der Struktur eines Systems verstanden.
Einen anderen Aspekt zeigt die fachliche Struktur wie in Abb. 2. Sie ist stabil: So lange Kunden Dinge bestellen, die ihnen anschließend berechnet und an sie geliefert werden, ist die Aufteilung sinnvoll. Sie sollte die Änderungen in der Domäne am besten unterstützen, weil sie sich konsequent an ihr orientiert.
Technologien
Technologien sind ein Mittel zum Zweck. Was ist dann der Wert moderner Technologien? Sie führen vielleicht zu einfach änderbareren System. In Wirklichkeit hängt die Änderbarkeit aber vor allem davon ab, wie gut sich Entwickler:innen mit den jeweiligen Technologien auskennen. Technologien können bestimmte Qualitätsmerkmale des Systems beeinflussen: Wenn es beispielsweise aufgrund des Alters keine Updates einer Technologie mehr gibt, kann das zu einem Sicherheitsproblem führen.
Es gibt im Bereich Software-Entwicklung ständig neue Technologien. Und wie schon erwähnt sind alte Technologien beispielsweise wegen Sicherheitsproblemen irgendwann nicht mehr tragbar. Projekte müssen sich also darauf einstellen, dass sie irgendwann zu einer neuen Technologie wechseln müssen.
Daher scheint es sinnvoll, den Code im System technologieunabhängig zu gestalten. Aber der Wechsel einer Technologie passiert nur selten. Vielleicht migriert man ein System einmal oder zweimal auf eine neue Datenbank. Die bei weitem überwiegende Mehrheit der Änderungen werden fachliche Änderungen sein. Außerdem hat die Änderung einer Technologie oft auch fachliche Auswirkungen. Wenn man eine Anwendung nicht nur auf dem Desktop, sondern auch mobil nutzen will, kann das auch Möglichkeiten für neue Fachlichkeit ergeben. So kann man mit einem Telefon auch mobil den Barcode von Waren scannen. Das ist auf dem Desktop kaum sinnvoll.
Bei den meisten Beratungseinsätzen geht es darum, ein System mit einer alten Technologie und alter Logik zu migrieren. Die Logik ist in diesen Fällen oft schlecht strukturiert und schlecht weiterentwickelbar. Wenn dieser Code technologieunabhängig wäre und einfach auf eine neue Technologie portiert werden könnte, würde man ihn dennoch vermutlich neu schreiben, weil die Qualität so schlecht ist. Technologieunabhängiger Code bringt also nicht so viel für die Zukunftssicherheit. Dennoch ist technologieunabhängiger Code sinnvoll: Er ist einfacher zu verstehen und daher auch einfacher anpassbar.
Ob man überhaupt immer „neue“ Technologien nutzen muss, ist eine offene Frage. Eine Rolle spielt, ob Entwickler:innen mit der Technologie noch produktiv arbeiten können. Das hängt jedoch von den jeweiligen Teams ab: Was für ein Team wie eine Technologie aus der Steinzeit wirkt, ist für ein anderes Team akzeptabel. Das kann man sich zunutze machen: Statt in die Migration in eine neue Technologie zu investieren, kann man Entwickler:innen in den genutzten Technologien ausbilden oder Entwickler:innen mit Know-How in den jeweiligen Technologien einstellen. Das hilft natürlich nicht, wenn es keine Sicherheitsupdate für die Technologie mehr gibt.
Als weitere Vertiefung sei noch die Stream-Aufzeichnung zum Thema empfohlen.