Breaking Changes in Microservices
Wie sich die Service-Landschaft wandeln und weiterentwickeln kann
Stellen wir uns vor, wir stehen am Anfang eines großen Software-Neuentwicklungsprojekts. Fachlich sind erstmal nur ein paar Überschriften bekannt, klar ist: es soll das Altsystem abgelöst werden. Technisch gibt es recht klare Vorstellungen bzgl. der Software-Architektur: es sollen Microservices sein. Bei der Neuentwicklung soll das ganze System neu gedacht und aufgebaut werden, fachlich wie technisch – und nicht etwa das Altsystem "einfach nur" technisch transformiert werden.
Wir dürfen also von der grünen Wiese aus starten und haben im Scope des Projekts ein "unbestelltes Feld" mit vielen Freiheiten – ich finde, das sind die schönsten Projekte! Doch was brauchen wir zunächst, um einen Architekturentwurf sauber starten zu können? Und was kann passieren, wenn frühzeitig entwickelt werden soll und wir beim Service-Schnitt und der -Kopplung nicht aufpassen? Und die Microservices-Architektur anschließend aber bei der Weiterentwicklung fundamentalen "Breaking Changes" standhalten muss, die sich nicht nur auf einzelne Services, sondern auf ganze Service-Konglomerate auswirken? Das soll der nachfolgende Erfahrungsbericht und die aus diesen Erfahrungen gezogenen Lehren zeigen.
Ausgangslage – das komplexe "Grüne-Wiese-Software-Projekt"
In den ersten Kick-Off-Terminen werden erste Daten zum Projektumfang bekanntgegeben. Es handelt sich um das zentrale System eines stark durch Zukäufe wachsenden großen Unternehmens, das perspektivisch an Dutzenden Standorten zur Abwicklung der zentralen Geschäftsfälle eingesetzt werden soll. Die Kernaufgaben des Systems sind grob umrissen, allerdings gibt es Hinweise auf standortspezifische Ausprägungen, die perspektivisch unterstützt werden sollen. Allein die Kernaufgaben sind schon recht vielfältig und reichen von unterschiedlichen operativen Prozessen bis zu einem Kern-Fakturierungsprozess mit verschiedenen In- und Outputs, an dem insgesamt sehr viele Umsysteme und auch Peripherie-Geräte beteiligt werden sollen.
Damit ist nicht nur der Scope des Projekts selbst sehr komplex, auch die Umsysteme sind es. Proportional zum wachsenden Scope wächst auch der Kreis von Stakeholdern, der in Gänze aber nicht bekannt ist. Die Ziele des Projekts sind auch ambitioniert, geht es doch darum, einen tragfähigen, aber "irgendwie" erweiterbaren Standard-Prozess zu implementieren. Mit steigender Anzahl an Zielen wachsen der Scope und dadurch wiederum die Anzahl potenzieller Stakeholder. Dieses wechselseitige Zusammenspiel der drei Komponenten, anhand derer man – wie ich finde – gut die Komplexität unseres Projektes erklären kann, ist in Abb. 1 dargestellt [1].
Die Erwartungshaltung an das Projekt, das durch die IT-Abteilung durchgeführt wird, ist, dass dieses durch eigene Kraft einen Standard etabliert, mit dem alle Standorte arbeiten können. Ohne Prozesse oder derlei Dokumentationen auf strategischer Ebene. Und: die neue Software soll schnell im Sinne eines Minimum Viable Product (MVP) eingesetzt werden können, um so einerseits frühes Nutzer-Feedback zu erhalten und andererseits strategisch im Unternehmen Sichtbarkeit zu erlangen, das MVP aber in einem iterativen Prozess laufend und möglichst günstig weiterzuentwickeln. Überspitzt und umgangssprachlich gesagt lautet die Erwartungshaltung an das Projekt: "Fahrt schon mal los! Wir sind ja agil und die Software ist flexibel!".
Das sind zu Beginn des Projekts viele Randbedingungen, die man erstmal miteinander in Einklang bringen muss. Als Softwareentwickler möchten wir sauber starten, d. h. eine saubere und langlebige Architektur entwerfen, welche die fachlichen und architektonischen Anforderungen erfüllt und möglichst gut nach den gängigen Architekturprinzipien entworfen wurde. Mithilfe der Architekturprinzipien streben wir an, dass Änderungen und Erweiterungen kurz- bis langfristig in der Software möglichst günstig umzusetzen sind.
Das an sich ist schon eine schwierige Aufgabe. Und sie wird umso herausfordernder, je komplexer der Problemraum unseres Projektes ist. Carola Lilienthal bezeichnet dies als "essenzielle Komplexität" – essenziell, da die abzubildende Fachlichkeit selbst diese Komplexität mit sich bringt [2].
Das Soll – der Clean-Start für die Software-Architektur und -Entwicklung
Okay, ein komplexes Vorhaben, aber irgendwie müssen wir ja starten. Wie können wir nun in diesem Spannungsfeld aus hoher fachlicher Komplexität, dem Projektziel "früh einsetzbares MVP" und unbekannten Anforderungen mit dem gesetzten Architekturstil der Microservices eine saubere Software-Architektur aufbauen?
Gernot Starke und Peter Hruschka sprechen dabei vom "Clean-Start", für den Folgendes bekannt sein müsse [1]:
- Fachliche Visionen und (langfristige) Ziele, z. B. in Form eines Produktkoffers,
- alle Stakeholder, insbesondere Key Player, aber auch alle weiteren einflussreichen und interessierten Stakeholder und
- die Kern-Aufgaben/ der Scope unseres Systems.
Auf den Scope haben wir Einfluss, während wir auf die Umsysteme keinen oder nur begrenzten haben (z. B. können wir Schnittstellenänderungen anfordern, d. h. aber nicht, dass diese auch von den Fremdanbietern umgesetzt werden). Umsysteme müssen wir daher zwingend kennen, da wir durch sie ja ggf. in unserer Lösung eingeschränkt werden (Faustformel für Software-Architekten: Kenne deine Schnittstellen!).
Wir müssen also bei den ersten Analysen in die "Breite" gehen, um alle für unsere grundlegenden Architekturentscheidungen notwendigen Informationen zu haben. Um aber nun im Rahmen eines iterativen Entwicklungsprozesses einen "früh einsetzbaren MVP" zu erreichen, müssen wir die für die ersten Iterationen notwendigen Fachthemen auch in die "Tiefe" analysieren – und neben der Architektur müssen zudem andere am Entwicklungsprozess beteiligte Rollen von der Entwicklung bis hin zur Qualitätssicherung eine Sichtweite in die "Breite" und in die "Tiefe" haben. Gernot Starke und Peter Hruschka haben das mit dem in Abb. 2 dargestellten "T-Shaped-Modell" synthetisiert.
Stellen wir uns in unserem Projekt vor, dass wir zunächst einen operativen Prozess implementieren sollen, der auf einem tragfähigen Systemkern beruhen soll. Der Systemkern besteht aus zentralen Fachklassen bzw. einem zentralen Datenmodell, das als "tragende Säule" für viele unterschiedliche operative Prozesse gilt, die in einem Standard-Fakturierungsprozess mit anschließend unterschiedlichen Outputs resultieren. Um die zentralen Fachklassen des Systemkerns tragfähig definieren zu können, müssen wir in der Software-Architektur also in der Breite analysieren, wofür diese perspektivisch überall eingesetzt werden. Den operativen Prozess müssen wir in die Tiefe analysiert haben, bevor wir dafür einen tragfähigen Komponenten-(Microservice-)Schnitt mit Zerlegung in Fachklassen (Datenmodell) und geeignete Schnittstellen entwerfen können.
Was kann uns dabei helfen, wenn wir ein so komplexes Projekt haben? Wir benötigen dafür Orientierungshilfen und fachliche Expertise von außen, z. B. in Form einer Prozessdokumentation, Unterstützung von Fachexperten oder Wissensträgern, vielleicht müssen wir auch das Altsystem analysieren.
Das Ist – das Problem mit der Projektdynamik und der engen Kopplung
Wir wissen also, was wir tun müssten, um eine gute Architektur im Sinne eines Clean-Starts aufzubauen und dass wir dafür auf projekt-externen Input angewiesen sind. Doch es kommt anders: Stellen wir uns einmal vor, es starteten mehrere Entwicklungsteams gleichzeitig und es entwickle sich eine ambivalente – ja fast toxische – Projektdynamik. Warum ambivalent? Weil kurzfristig eine aus externer Sicht funktionsfähige Software in Form eines MVPs entsteht, diese irgendwann in der Zukunft aber nicht mehr grundlegenden Breaking Changes gerecht wird (dazu später mehr). Mangels Zeit, Ressourcen – insbesondere Fachexperten, die fachlichen Input liefern – und aufgrund des Kostendrucks werden nicht alle relevanten Anforderungen in die Breite analysiert, aber mehrere Entwicklungsteams starten parallel und in einem iterativen Prozess mit ihren Arbeitspaketen. Team 1 entwickelt nun den Systemkern mit einem "Produkt"-Fachkonstrukt (Datenmodell) in seinem eigenen Microservice. Team 2 entwickelt den operativen Prozess in einem anderen Microservice, wofür es das "Produkt"-Konstrukt von Team 1 braucht. Team 3 entwickelt einen nachgelagerten Warehouse-Service, der beide Konstrukte zu brauchen scheint.
Schnell verselbständigt sich das "Replikations-Pattern". Für Team 2 scheint es zunächst einfacher zu sein, die ganzen "Produkt"-Entities mit allem, was daran hängt, komplett zu replizieren als ein eigenes entkoppeltes Datenmodell im Sinne eines "Domain-driven Designs" zu definieren und damit den Microservice als fachlich lose gekoppelten "Bounded Context" zu bauen [3]. Denn: Es ist ja noch nicht klar, was der Microservice künftig genau von dem "Produkt" im operativen Prozess benötigt und es stehen keine Fachexperten zur Verfügung, um genau diese Frage vorausschauend zu beantworten. Für Team 3 wiederum gilt genau das gleiche, nur dass dieses zusätzlich auch die Fachklassen des operativen Prozesses repliziert. Replizieren meint: Wird ein Produkt erzeugt, wird vom Produzenten-Service ein created-Event über den Message-Broker geschickt. Die Konsumenten-Services reagieren darauf asynchron und replizieren nahezu 1:1 das Produkt in ihre eigenen Datenbanken, die gemäß Microservice-Pattern von den Datenbanken anderer Microservices getrennt sind. Dazu nutzen sie in ihren Event-Handler-Implementationen einen REST-Client, der die entsprechenden Produkt-Daten über eine Schnittstelle im Produzenten nachlädt, mappt und in der eigenen Persistenzschicht abspeichert. Zwar gibt es im Konsumenten und in der Schnittstelle des Produzenten einen "Anti-Corruption-Layer (ACL)", beide implementieren ein Mapping zwischen ihrem internen Datenmodell und den REST-Ressourcen, dennoch ist dieses fast ein 1:1-Mapping. Das führt dazu, dass sich die Daten-Schemata der verschiedenen Services strukturell sehr ähneln, so haben alle ein Produkt mit fast allen Attributen, das wiederum aus n "Einträgen" besteht. Dadurch entsteht zwischen den Konsumenten und Produzenten eine "Conformist"-Beziehung, d. h. die Konsumenten-Daten-Schemata sind konform mit den Produzenten [3]. Technisch sind die Datenbankschemata zwar entkoppelt, fachlich entstehen durch den "Conformist" aber eng gekoppelte Domänenmodelle.
Wie genau die Architektur der eng gekoppelten Domänenmodelle weiter wächst und warum das problematisch ist, soll nun folgendes Beispiel aus unserem exemplarischen Projekt zeigen. Die großen blauen Kästen zeigen die Services, darin enthalten sind beispielhafte Fachklassen des Datenmodells. Fachklassen mit identischer Farbe sollen zeigen, dass diese repliziert wurden. Die Pfeile zeigen Abhängigkeiten sowohl zwischen den Services als auch in den Datenschemata zwischen den Fachklassen.
Wie in Abb. 3 dargestellt, baut Team 2 den "Measuring"-Service und repliziert sich die Organization und die Location. Die eigene Fachklasse "Operativer Prozess" referenziert die Fachklassen "Organization" und "Location".
Doch wie Abb. 4 zeigt, stellt sich die Fachlichkeit nach ein paar Iterationen anders dar. Team 2 legt fest, dass es nicht nur ums "Messen" geht, sondern der Service einen größeren operativen Prozess abbildet. Es entscheidet sich, keinen gesonderten Service zu implementieren, sondern den "Measuring"-Service zu erweitern und umzubenennen.
Und wie Abb. 5 zeigt, spielt da nun das "Product" von Team 1 eine Rolle. Team 2 entscheidet sich, das "Product" zu replizieren, "da das erstmal einfacher ist".
Wie Abb. 6 zeigt, wachsen die Abhängigkeiten des "Operation Service" dynamisch: so soll nach ein paar weiteren Iterationen der Operation Service den Invoicing Service von Team 3 beliefern anstatt umgekehrt.
Was ist hier passiert, was unsere Architektur nach und nach erodieren lässt?
Wegen des mangelnden Blicks in die Breite werden Service-Schnitte aufgrund der organisatorischen Trennung der Entwicklungsteams getroffen, sodass diese erstmal möglichst unabhängig voneinander arbeiten können. Teils geschieht dies durch das Prinzip "nach bestem Wissen und Gewissen".
Das Resultat ist ein architektonisch gemäß "Stable-Dependencies-Prinzip" sehr instabiler "Operation Service" [4], der von sehr viel anderen über Schnittstellen-Interaktionen abhängt, von dem aber wiederum kein anderer Service abhängt, der also eine hohe Kopplung nach außen hat. Dessen Datenmodell aber auch intern wegen des Replikations-Patterns von Fachklassen anderer Domänen abhängig ist. Es handelt sich dabei nicht nur um rein schematische Abhängigkeiten, sondern darauf wird mithilfe des objektrelationalen Mappers (ORM) sogar Business-Logik implementiert, z. B. müssen in der Business-Logik des "Operation-Service" Daten aus den "Einträgen" des jeweiligen "Products" ausgewertet werden.
Und im Systembild kommt es noch schlimmer: Mit der Zeit wird das "Product" immer weiter in andere Services "gestreut" und dort jeweils wieder tief in den Schemata und Business-Logiken "verwurzelt"
Die Software aber funktioniert, sie wird schnell erfolgreich ausgeliefert, in Betrieb genommen und von ersten Standorten genutzt. Das wiederum befeuert die toxische Projektdynamik: denn was gut funktioniert, kann erstmal weiter genutzt werden. Die Folge-Wirkung zeigt Abb. 7: Die Systemarchitektur der Microservices erodiert immer mehr und es entsteht eine aus Service-Sicht außen (über viele Punkt-zu-Punkt-Schnittstellen) und innen (abhängige Datenbankschemata und Business-Logik) eng gekoppelte Systemlandschaft.
Das Ist – Breaking Change trifft auf starre Architektur
Die "essenzielle" Komplexität der Fachlichkeit bringt es irgendwann mit sich: Mit steigender Anzahl an Niederlassungen, die unsere Software einsetzen (wollen), wachsen auch die Anforderungen. Wir erhalten nun Anforderungen, die wir zu Beginn unserer Architekturentscheidungen nicht kannten, denn wir hatten ja wie o. g. nicht den Blick in die Breite.
Die Gefahr, dass nun grundlegende Fachkonstrukte brechend (breaking) verändert werden müssen, ist da recht hoch – und der Breaking Change kommt schon sehr bald von unseren Requirements Engineers unter Leitung eines neuen Product Owners. Dieser hat erkannt, dass das bisher von uns Softwareentwicklern als "tragfähig" empfundene Konstrukt des "Products" gar nicht tragfähig für weitere Standorte und Anwendungsfälle ist und durch ein komplexeres Fach-Konstrukt ersetzt werden muss.
Nun trifft der Breaking Change auf unsere Software-Architektur, die aufgrund der oben beschriebenen Kopplung recht starr ist. Die Änderung ist nicht einfach umzusetzen, denn jede Änderung an einer Stelle – einer Fachklasse oder einem Microservice – führt zu einer Kaskade von weiteren Änderungen, die nötig sind [2]. Abb. 8 zeigt das eindrucksvoll: die ockergelb markierte Fachklasse "Produkt" müsste in allen Services, die dieses verwenden, durch ein neues Konstrukt ersetzt werden. Dies bedeutet: Schemata-Änderungen, Änderungen an jeder Businesslogik, wiederum Änderungen in nachgelagerten Schnittstellen für andere Konsumenten-Services, was wiederum Änderungen in anderen Services bedeutet usw.
Abb. 9 soll eine Analogie zur realen Welt aufzeigen: Wir haben im Prinzip eine Reihenhaus-Siedlung von Microservices gebaut, wobei jedes Reihenhaus ein eigener Microservice ist und alle Microservices durch gemeinsame Querbalken miteinander verbunden sind. Einer dieser Querbalken ist das "Produkt", das in jedem Microservice als tragende Säule fungiert. Reißt der Querbalken, reißen auch die tragenden Säulen und vielleicht nachgelagerte Services/Reihenhäuser in einer Art Domino-Effekt.
Der Erfahrungsbericht – Umgang mit den Breaking Changes
Neben dem hier gezeigten gab es noch eine ganze Reihe weiterer Breaking Changes, d. h. es war nicht nur ein zentrales Fachkonstrukt, das ausgetauscht werden sollte, sondern gleich mehrere. In der Analogie mussten also gleich mehrere Querbalken ausgetauscht werden. Doch wie sind diese nun auszutauschen, ohne dass die tragenden Säulen der einzelnen Services einreißen? In diesem Abschnitt soll erläutert werden, wie dies in dem exemplarischen Projekt erreicht werden konnte.
Zunächst musste, wie in Abb. 10 gezeigt, entlang der verschiedenen Stufen im Entwicklungsprozess entschieden werden, wie mit dem Breaking Change umzugehen ist.
Angefangen bei der Stufe 0 – der Makro-Architektur. Hier stellte der System-Architekt fest, dass die Breaking Changes so weitreichend seien, dass grundlegend entschieden werden müsse, wie die Makro-Architektur organisiert werden muss. Dabei musste auch die Frage beantwortet werden, wie mit dem bereits laufenden Produktionsbetrieb umzugehen ist. Daher fiel auf dieser Stufe die Entscheidung, die vor dem Breaking Change existierende Service-Landschaft als eigenes System oder eigenen V1-Stack parallel ohne Weiterentwicklung weiter zu betreiben und die neu zu bauende Service-Landschaft in einem neuen System oder V2-Stack einzubauen. Dabei können existierende Services aus V1 sowohl im V1- als auch V2-Stack und damit mithilfe von Branching aus derselben Code-Base genutzt werden. Es können aber auch neue Services in V2 verortet werden, die es in V1 nicht gab. Zeitgleich wurden auf Ebene der Makro-Architektur grundlegende Entwicklungsrichtlinien und Leitplanken für die V2-Entwicklung definiert, um diese effizienter und einheitlicher zu gestalten. Dazu zählten z. B. ein Bedienkonzept, Querschnittsfunktionen, Konventionen zur Code-Struktur, allgemeine Microservice-Design-Patterns, aber auch ein regelmäßiger Austausch aller Software-Architekten, die auf der Makro-Architektur aufbauen müssen.
Damit hat die Stufe der Makro-Architektur einen Lösungsraum für die nächste Stufe 1 – die Microservice-Architektur – definiert. Auf dieser Stufe mussten dann die Software-Architekten für ihre einzelnen zu verantwortenden Services entscheiden, wie darin architektonisch mit den entsprechenden Breaking Changes umzugehen ist. Dazu musste der Software-Architekt anhand des oben erwähnten Grads der inneren und äußeren Kopplung des Services sowie anhand der Konformität zu den neuen Entwicklungsrichtlinien und Leitplanken der Makro-Architektur entscheiden, ob...
- der Service gar nicht weiter verändert werden muss,
- der Service weiterentwickelbar ist,
- der Service durch eine Neuentwicklung ersetzt werden muss oder
- gar in V2 auf den Service verzichtet werden kann.
So gab es z. B. Stammdaten-Services, die ihrerseits gar nicht von dem Breaking Change betroffen waren, selbst unabhängig von anderen Services waren und daher nicht verändert werden mussten. Wieder andere Services waren zwar von dem Breaking Change insofern betroffen, dass sich eine Input-Fachklasse eines abhängigen Services änderte, aber ihr Datenmodell war so entkoppelt, dass nur ein Mapping an wenigen Stellen angepasst werden oder eine recht einfache Migration per SQL und Code-Changes stattfinden musste – diese Services waren dann in einer neuen V2-Lebenslinie (abgebildet über Branching) weiter entwickelbar. Für manchen sehr eng gekoppelten Service wurde festgestellt, dass es günstiger ist, den Breaking Change im Rahmen einer Neuentwicklung des Services umzusetzen und dabei dann eine möglichst lose Kopplung anzustreben. Anderer Services waren schlichtweg nicht mehr relevant, da sie fachlich durch den Breaking Change obsolet wurden.
Auf der nächsten Stufe 2 – der Entwicklung des einzelnen Microservice – mussten die Entwickler nun die Entscheidung der Software-Architekten der vorherigen Stufe umsetzen. Wenn der Service gar nicht verändert werden musste, gab es für die Entwickler da nichts zu tun. Wenn er hingegen weiterentwickelt werden sollte, mussten die Entwickler in Zusammenarbeit mit ihrem Architekten eine Migration vom alten aufs neue Schema bauen, z. B. mithilfe einer Datenbankversionierung wie Flyway, Code-Refactorings und ggf. Implementation der neuen Funktionen. Diese Änderungen mussten sie dann im V2-Branch des entsprechenden Git-Repositories durchführen. Wurde auf der vorherigen Stufe entschieden, den Service durch eine Neuentwicklung zu ersetzen, mussten die Entwickler einen neuen Namen für den neuen Service finden und dieser musste mitsamt Git-Repository und Code-Base, Erweiterung des Stacks etc. generiert und in der CI/CD-Pipeline lauffähig initialisiert werden. Danach galt es, die neuen Funktionen zu implementieren, die Schnittstellen anderer Services anzubinden, den Service in den neuen Geschäftsprozess gemäß des Breaking Changes einzubetten etc.
In Stufe 3 – dem Betrieb durch die DevOps-Kollegen – mussten die Entscheidungen der vorherigen Stufen betrieblich berücksichtigt werden. Dazu mussten zunächst die Vorgaben der Makro-Architektur umgesetzt werden, d. h. der neue V2-Stack aufgesetzt und der alte V1-Stack separiert werden. Konkret wurden dazu eigene voneinander getrennte Infrastruktur-Elemente installiert, z. B. getrennte Container-Manager bzw. -Cluster, getrennte Datenbanken und Message-Broker. Die V1- und V2-Systeme wurden durch unterschiedliche DNS und IPs zugänglich gemacht. Dabei mussten jeweils für V1 und V2 die unterschiedlichen Stages (DEV-, Integration-, Pre-, Prod-System) voneinander separiert werden. Die Entscheidungen der Stufen 2 und 1 – d. h. der einzelnen Microservice-Architekten und –Entwickler – wurden durch die DevOps-Kollegen natürlich ebenfalls berücksichtigt. Sie bereiteten entsprechend V1- und V2-Branches in den Repositories und unterschiedliche CI-Pipelines je nach Branch vor (V1 baut V1-Images, deployt auf dem V1-Stack, V2 baut V2-Images, deployt auf V2). Sollte ein Service gar nicht mehr in V2 genutzt werden, konnten die DevOps-Kollegen diesen aus dem V2-Stack entfernen, mussten aber dennoch den V1-Branch zwecks eventuellem Hotfixings im V1-System vorbereiten. Bei einer Neuentwicklung mussten sie nur einen V2-Branch, aber keinen V1 einrichten, während der alte abgelöste Service umgekehrt nur einen V1- aber keinen V2-Branch erhielt. Weiter entwickelte oder gar unveränderte Services erhielten sowohl einen V1- als auch einen V2-Branch.
Ein besonderer Fall für die Stufen 2 und 1 stellte die monolithische UI dar: Sie war dank sehr guter Strukturierung durch Paketierung und strenge, durch Unit-Tests abgesicherte, Architekturregeln modular aufgebaut (unabhängige Module je Microservice) und konnte so einfach weiterentwickelt werden. Dazu mussten die Entwickler entsprechend im V2-Branch weiterentwickeln und dort später veraltete Module aus V1 entfernen. Ähnlich verhielt es sich mit dem Gateway (Reverse Proxy zwischen Web-Clients und den Services), das ebenfalls mithilfe des V2-Branches weiterentwickelt werden konnte – so wurde exklusiv in V2 sogar eine Service-Registry eingeführt und mit dem Gateway gekoppelt.
Die dabei entstandene System-Architektur der nunmehr "zwei Systeme" (V1 und V2) ist in Abb. 11 dargestellt. Wichtig war insbesondere die Trennung der Datenbanken und Message-Broker, um so in V2 eine saubere und isolierte Datenhaltung aufzubauen und andererseits nicht die beiden Systeme miteinander zu koppeln (z. B., wenn ein Event in V1 entsteht und aus Versehen in V2 konsumiert würde). Einzig die Umsysteme überschneiden sich, was jedoch wegen der Mandantentrennung unproblematisch war, d. h. die Standorte bzw. Firmen, die V1 weiterhin nutzen, nutzen nicht zeitgleich V2 und umgekehrt und in den Umsystemen gilt ebenfalls eine Mandantentrennung.
Damit sind die Systeme V1 und V2 derart entkoppelt, dass man den V1-Stack ganz "abklemmen" oder zumindest die Datenbank archivieren kann, sobald die Standorte operativ von V1 auf V2 gewechselt sind. Wird V1 abgeklemmt, würde V2 davon nicht beeinflusst werden.
Fazit
Die Breaking Changes waren eine ziemliche Herausforderung für die entstandene "Replikations"-Architektur. Dennoch konnte das System von V1 auf V2 so weiterentwickelt werden, dass eine große Anzahl von Services wiederverwendet werden konnte, was in Abb. 12 sichtbar wird. Die Schnittmengen zwischen beiden Systemen sind trotz der Breaking Changes recht groß. Damit hat sich die Microservices-Architektur insofern ausgezahlt, als dass nicht das ganze System, sondern nur Teile verworfen werden mussten.
Dennoch ist der organisatorische und betriebliche Overhead der Weiterentwicklung von V1 auf V2 sehr groß, was zu hohen Kosten führt. Wären die Services gemäß Architekturprinzipien und im Idealfall gemäß eines Domain-driven Designs als möglichst außen wie innen lose gekoppelte "Bounded Contexts" entworfen worden, wären möglicherweise mehr Microservices weiter entwickelbar gewesen.
Allerdings sind fachliche Breaking Changes immer teuer und haben am Ende schon das ein oder andere Aus einer ganzen Software bedeutet. Daher sollte in Zukunft sehr gut auf einen fachlichen "Clean Start" geachtet werden, bevor grundlegende und nur schwer revidierbare Architektur-Entscheidungen zum Service-Schnitt und der Service-Kopplung getroffen werden. So sollte einer möglichen sich selbst verstärkenden Projektdynamik Einhalt geboten werden und den Entwicklungs-Teams klare Vorgaben zum Service-Schnitt und möglichst loser Kopplung gemacht werden, ohne dass diese unter Zeitdruck selbst Entscheidungen nach "bestem Wissen und Gewissen" treffen. Hier sind wir als Software-Architekten von Anfang an auf sehr guten Input durch Fachexperten und Verständnis sowie Unterstützung durchs Management angewiesen, um eine langfristig wartbare Architektur bauen zu können – soll möglichst schnell ein tragfähiger MVP gebaut werden, sollten durch das Management also umso mehr im Projekt die Voraussetzungen dafür geschaffen werden, dass Requirements-Engineers und Software-Architekten zuerst ausreichend in die "Breite" schauen können, bevor entwickelt wird.
Um abschließend die Analogie aufzugreifen: Denn dann haben Software-Architekten und -Entwickler die besten Voraussetzungen, eine lose gekoppelte Einzelhaus- anstelle einer eng gekoppelte Reihenhaus-Siedlung zu bauen.
- P. Hruschka, Dr. G. Starke; 2020: Requirements-Skills erfolgreicher Softwareteams. Leanpub.
- Dr. C. Lilienthal; 2019: Langlebige Software-Architekturen: Technische Schulden analysieren, begrenzen und abbauen. Dpunkt.Verlag
- V. Vernon; 2017: Domain-Driven Design kompakt. Dpunkt.Verlag
- R.C. Martin; 2018: Clean architecture: a craftsman's guide to software structure and design. Prentice Hall.