Warum ich gerne mit Fitness Functions an „evolutionären Architekturen“ arbeite!

Bei der Entwicklung von Softwaresystemen sind wir als Entwickler und Architekten es schon gewöhnt mit häufig ändernden Requirements umzugehen. Agilität ist längst kein Modewort mehr und praktisch jeder, der Softwaresysteme entwickelt, arbeitet heute (oder versucht es zumindest ;-)) nach agilen Softwareentwicklungs-Prinzipien zu arbeiten.

Allerdings erlebe ich noch immer oft, dass die Arbeit an der Softwarearchitektur eines Systems nicht so richtig in diese agile und flexible Zeit passt.

Was also hat eine „evolutionäre Architektur“ mit agiler Softwareentwicklung zu tun bzw. wie wird hier Agilität mit Softwarearchitekturarbeit verbunden? 

Nun, Evolution umfasst im Wesentlichen die Fähigkeit zur Anpassung. Für die Softwareentwicklung bzw. Softwarearchitektur trifft diese Definition vielleicht nicht genau so zu, wie es dies für die Natur tut, aufgrund der unterschiedlichen Zeitverhältnisse. (Es dauert einfach zu lange, bis sich ein Organismus anpasst, zumindest wenn man Zeiträume verwendet werden, die für den Menschen oder die Softwareentwicklung nützlich sind). Vielmehr geht es um das Schlüsselprinzip der Anpassung: Die Anpassung an ein sich änderndes Umfeld und die Auswahl der „besten“ Lösung ist quasi eingebaut (oder neu-deutsch: „built-in“) im Prozess und umfasst explizit auch die Softwarearchitektur.

Das Ziel einer evolutionären Architektur besteht darin, kleine, inkrementelle Änderungen zu unterstützen, die sich über mehrere Dimensionen (später dazu mehr, was mit Dimensionen gemeint ist) der Softwarearchitektur erstrecken. Warum klein und inkrementell? Weil wir in den letzten 20+ Jahren gelernt haben, dass große und vor allem langsame Veränderungen (bspw. wenige Releases pro Jahr) sehr schmerzhaft sein können, da sie dazu neigen, nicht erfolgreich zu sein. Damit sind sie das genaue Gegenteil von flexibel und anpassungsfähig.

Um sich also anpassen zu können, braucht man eine Architektur die „flexibel ist“ und einen Prozess, der darauf abzielt Flexibilität herzustellen und zu bewahren. Ein wichtiger, wenn nicht der wichtigste Aspekt eines solchen Prozesses, ist es alle Beteiligten an dieser ständigen Veränderung teilhaben zu lassen und mitzunehmen. Das ist es ja, was Agilität ausmacht. Das ist der Gegensatz zu starren Wasserfallprozesse, an die sich manche von euch vielleicht noch erinnern können – zuerst wurde die Architektur entwickelt und danach durften die Entwickler innerhalb dieser Grenzen loslegen und wurden von den Architekten „kontrolliert“. Das war das genaue Gegenteil dessen, was wir mit einem agilen und evolutionären Ansatz heute erreichen möchten.

Technische Lösungsansätze bzw. Prinzipien, die einem helfen flexible Architekturen zu entwickeln sind per-se nicht neu – hier 3 sehr oberflächliche Prinzipien, die heutzutage jeder kennt:

  1. Modularität: Systeme in kleine, unabhängige Module unterteilen, die unabhängig voneinander entwickelt, getestet und released werden können (manche denken jetzt vielleicht an Microservices, das ist ein Modularitätsansatz; ist aber nicht die einzige Modularität, die hier gemeint ist sondern eben viel allgemeiner). 
    Dies erleichtert es kleinteilige Anpassungen vorzunehmen und verringert das Risiko von negativen und unbekannten Seiteneffekten.
  2. Fokus auf Tests: Implementierung von robusten Testprozessen, um sicherzustellen, dass Änderungen am System keine unbeabsichtigten Folgen haben. Dazu gehören Unit-Tests, Integrationstests und End-to-End-Tests als auch Qualitäts- od. Architekturtests. Mehr dazu gleich beim Thema „Fitness Functions“.
  3. Verwendung von CI/CD-Pipelines (Continuous Integration and Continuous Deployment): Automatisieren des Entwicklungs- Test- Releaseprozesses mit CI/CD-Pipelines. Das Ziel ist schnell und einfach Änderungen an einem System releasen zu können.

Indem man diese „Good Practices“ beherzigt, hat man eine gute Chance, eine Softwarearchitektur zu erstellen, die flexibel und anpassungsfähig ist. Meist ist es jedoch schwierig die Flexibilität dauerhaft aufrecht zu erhalten, da durch zusätzliche Anforderungen die ursprüngliche Anpassungsfähigkeit immer mehr verwässert wird. Dies wird auch durch mangelnden Überblick über die vielen Aspekte und Dimensionen eines Softwaresystems erschwert.

Meist ist es jedoch schwierig die Flexibilität in Softwaresystemen dauerhaft aufrecht zu erhalten!

Ein Werkzeug oder eine Methode, die ich sehr schätze, da sie helfen kann den Fokus auf Flexibilität auch nachhaltig im Entwicklungs-Prozess zu verankern, nennt sich Fitness Functions. Wie in „Building Evolutionary Architectures“ von Pat Kua, Rebecca Parsons und Neil Ford beschrieben, ist eine Fitness Function eine objektive Funktion, die verwendet wird, um die Lösung in einer od. mehrerer architektonischen Dimensionen zu messen. 

Wenn ich Fitness Functions erkläre, beschreibe ich diese meist als Architekturtest, der in einer od. in mehreren Dimension Feedback gibt. Meist ist dieses Feedback die Messung eines Ziels und die Ausgabe der Implementierung ein numerischer Wert der eine Aussage zur Zielerreichung ermöglicht. 

Die Fitness Function ist die Definition eines Ziels, und die Überprüfung dieses Ziels wird üblicherweise als automatisierter Test oder kontinuierliche Messung implementiert, der als Teil einer CI/CD-Pipeline wiederholt ausgeführt wird.  Dies ermöglicht es konstant Feedback zu den wichtigsten Aspekten eines Software-Systems liefert. Eines dieser Ziele ist die Anpassungsfähigkeit oder Flexibilität der Architektur.

Da wir nach inkrementellen Verbesserungen in mehreren Dimensionen suchen, benötigen wir auch mehrere Fitness Functions (+ implementierte Tests), um Feedback in mehreren Dimensionen gleichzeitig zu erhalten. Ein anderes Wort, das ich für Dimension verwenden würde, wäre Qualitätsziel (od. manchmal auch Architekturziel). Die wichtigsten Qualitätsziele, bzw. deren Überkategorien sind beispielsweise (aus ISO 25010):

  • Funktionale Eignung
  • Performance
  • Kompatibilität
  • Usability
  • Zuverlässigkeit
  • Sicherheit
  • Wartbarkeit
  • Portabilität

Wir müssen mehrere dieser Ziele (= “Dimensionen“) gleichzeitig betrachten, da diese oft in einer gewissen Abhängigkeit zueinanderstehen und mit jeder kleinteiligen Änderung wieder ein neues „globales Optimum“ des gesamten Softwaresystems anstreben. Hier ein einfaches Beispiel einer typischen Abhängigkeit in einem Softwaresystem:

Angenommen, man erhöht die Wartbarkeit, indem man eine weitere Abstraktionsschicht bzw. bessere Modularität im System einführt. Durch diesen Schritt sinkt jedoch die Performance so signifikant, dass die Anwendung nicht mehr brauchbar ist. Es wäre eine Änderung am System, welche die Stakeholder nicht besonders glücklich macht.
=> Wir müssen also die Dimensionen „Modularität“ als auch „Performance“ gleichzeitig messen können um hier zuverlässig eine Aussage über die Qualität unserer Lösung (bzw. nach einer Änderung) treffen zu können.

Wir müssen mehrere dieser Ziele (= “Dimensionen“) gleichzeitig betrachten, …. und mit jeder kleinteiligen Änderung wieder ein neues „globales Optimum“ des gesamten Softwaresystems anstreben.

Um die wichtigsten Bereiche für solche Tests zu identifizieren, verwende ich gerne eine priorisierte Liste von Qualitäts- oder Architekturziele. Die Priorisierung erfolgt dabei mit den Stakeholdern gemeinsam.

Darauf aufbauend können wir konkrete (Veränderungs-)Szenarien entwickeln und darauf aufbauend Fitness Functions erstellen und automatisieren, die uns helfen, unsere Softwarelösung in diesen Bereichen zu testen und zu messen. Ein Bereich dieser Tests widmet sich der Flexibilität in den Bereichen, in denen wir am ehesten Veränderungen antizipieren. Diese messen wir kontinuierlich mit allen anderen wichtigen Qualitätszielen des Systems, um diese Ziele dauerhaft im Blick zu haben.

Der zweite Aspekt, der mindestens genauso wichtig ist, wie die richtige Auswahl der Qualitätsziele und die technische Umsetzung der Qualitäts- und Architekturtests, ist die Möglichkeit, die Definition, Auswahl und damit alle wichtigen Diskussionen rund um die architektonischen Ziele in den Softwareentwicklungsprozess des gesamten Teams einfließen zu lassen. Mit Qualitätszielen und den damit verknüpften Fitness Functions ist es einfach transparent die Ziele zu kommunizieren, zu diskutieren und diese anschließend kontinuierlich zu messen. Dies ermöglicht es dem gesamten Team, an Architekturdiskussionen teilzunehmen, und bietet eine objektive Herangehensweise an die Architekturarbeit für das gesamte Entwicklungsteam. Damit ist transparent und messbar was „gute Architektur“ ausmacht, da konkrete Werte und Ziele diskutiert werden können. Zusätzlich ist es ein gewisses Sicherheitsnetz, um schnell Dinge ausprobieren zu können und zu sehen, ob die Ziele immer noch erreicht werden.

Die effektive Zusammenarbeit von allen Beteiligten bei der Erstellung eines Softwaresystems ist oft genauso wichtig, wie das technische Wissen und die Erfahrung der Beteiligten. 

Mit Qualitätszielen und den damit verknüpften Fitness Functions ist es einfach transparent die Ziele zu kommunizieren, zu diskutieren und diese anschließend kontinuierlich zu messen.
Dies ermöglicht es dem gesamten Team, an Architekturdiskussionen teilzunehmen, und bietet eine objektive Herangehensweise an die Architekturarbeit für das gesamte Entwicklungsteam!

Die Arbeit mit Fitness Functions und Architekturtests sowie die Einbettung in agile Entwicklungsorganisationen ermöglicht es die effektive Zusammenarbeit weiter zu verbessern, mit dem Ziel, flexible und anpassungsfähige Softwaresysteme zu bauen – und evolutionär die Architektur an neue Marktbedürfnisse anzupassen.

Natürlich ist diese Methode keine Silver-Bullet, welche alle Probleme automatisch löst. Aber sie kann uns helfen kleinteilig änderbare Architekturen zu entwerfen und langfristig weiterzuentwickeln.

Eine weitere Einordnung zu Fitness Functions, deren Kategorisierung und Bestimmung der Prioritäten findest du auch in meinem Buch-Kapitel „The Fitness Function Testing Pyramid: An Analogy for Architectural Tests and Metrics“. (Buch: Software Architecture Metrics, O’Reilly, 2022)