Self contained systems

Ab einer gewissen Softwaregröße wird man feststellen, dass ein großer Monolith nicht mehr skalierbar ist. Spätestens wenn mehre Teams an diesem Stück Software arbeiten wird es immer schwieriger einen Monolithen erfolgreich zu bauen. Gründe hierfür können sein: 

  • Abhängigkeiten können nicht mehr zielgerichtet aktualisiert werden 
  • Es gibt unterschiedliche Auffassungen darüber, wie eine Software aussehen soll 
  • Zentrale Klassen haben keine Ownerschaft 
  • Buildfehler oder Bugs aufgrund von schlechten Commits 
  • Weniger Ownerschaft für den Gesamtcode 
  • Eine zu große unübersichtliche Codebasis 

Lösungen für große Softwaresysteme

  • Immer einen Konsens/t finden 

“Zwei Expert*innen – drei Meinungen?” – Wem kommt das bekannt vor? In der Regel werden Teams gesplittet, damit nicht jeder mit jedem sprechen muss und ein Kommunikationsnetzwerk möglichst klein ist. Wenn alle alles demokratisch abstimmen müssen, wird die Entwicklung sehr langsam sein. 

  • Ein Architekturteam nutzen 

Da liegt es doch nah, einfach eine entscheidende Person oder Gruppe zu bestimmen. Dies wurde in der (preagilen) Vergangenheit auch sehr häufig umgesetzt. Hierbei gibt es allerdings zwei Probleme 

  • Team setzt diese Entscheidungen nicht um 
    Wer entscheidet, welcher Personenkreis diese Entscheidungen treffen? Ist dieser Personenkreis auch für jede Entscheidung die richtige Person? Es wird immer Mittel und Wege geben, dass die ursprünglichen Entwickler*innen eine für sie falsche Anweisung nicht umsetzt. Neben vorsätzlichem Handeln kann es genauso gut sein, dass diese Personen von dieser Anweisung gar nicht wussten. 
  • Das Team ist ein Flaschenhals 
    Jede wichtige Entscheidung muss von diesem Team abgenickt worden sein und ggf. sogar umgesetzt werden. Dadurch werden Innovationen gestoppt und können nur sehr langsam umgesetzt werden. 
  • Sehr restriktive Feature-Branches 
    Wenn viele Menschen an einem Stück Software arbeiten, kann es immer zu Fehlern kommen. Menschen machen Fehler und das ist auch vollkommen in Ordnung. Sofern ein fehlerhaftes Stück Software im Main-Branch landet, werden alle Personen ausgebremst, welche das gleiche Software-Kompilat nutzen. Nehmen wir einen Java-Monolithen; dieser wird im schlimmsten Fall nicht mehr kompilieren und keiner kann für einen gewissen Zeitraum mehr arbeiten. Jetzt kommt berechtigterweise der Einwand, dass dafür eine CI Pipeline da ist! Das ist korrekt, trotzdem kann ein potenzieller Merge immer ein anderes Ergebnis liefern als der erfolgreiche Build auf dem Feature-Branch. Die einzige Lösung ist, gerebasede Branches auf den Main zu erlauben. Dies führt zu einem Mergestau und zu einer extremen Verlangsamung der Produktivität. 
  • Systeme schneiden 

Alle bisher beschriebenen Probleme basieren auf einem nicht perfekt laufenden Zusammenspiel zwischen unterschiedlichen Individuen. Alle Lösungen sind organisatorisch begründet und verlangsamen das System, da sie prozessual davor schützen sollen, technische Fehler zu machen. Dass Menschen in ihrem Zusammenleben aber Fehler machen, ist vollkommen natürlich und jedes System mit zwei Personen wird komplex. Die Lösung ist etwas Organisatorisches, technisch zu lösen. 

Ein häufiges Argument gegen das Schneiden von Systemen ist, dass man dies technisch nicht benötigt (keine Skalierung benötigt etc.). Der Faktor “Mensch” wird von Techies gerne einmal ignoriert, was aber ein wichtiger Faktor in der Entscheidung der Architektur ist.  

  • Microservicearchitektur 
    Es gibt eigentlich keine klassische Microservicearchitektur. Wenn man dieses Thema auf die Spitze treiben möchte, könnte man für jede Entität und für jede Operation einen eigenen Service erstellen, welcher unabhängig gewartet/erweitert/aktualisiert werden kann. 
    Ein großes Problem bei vielen Microservicearchitekturen ist eine starke Kopplung von Systemen. Diese sprechen sehr häufig direkt via REST-Services miteinander und tendieren dazu “verteilte Monolithen” zu werden. Für eine Änderung müssen unterschiedliche Services angefasst werden, es müssen mehrere Services gestartet werden und wenn ein Service ausfällt, ist ein ganzer Workflow down. Von Netzwerklatenzen mal abgesehen. 
  • Self contained systems 
    Self contained systems sind größer geschnittene “Microservices”, welche die Systeme in vertikalen Architekturen darstellen und worauf wir im Folgenden weiter eingehen: 

Self contained systems

Ein erstelltes System wird in die verschiedenen Teile einer Software unterteilt, z.B. Nutzerverwaltung, Buchhaltung und Bestellungen. Die Größe eines Vertikals kann beliebig variieren. Es kann also auch sein, dass Bestellungen noch einmal in mehrere Vertikale unterteilt wird. Das ist dann aber die eigentliche Architekturarbeit! 😉  
Jedes Vertikal besitzt ein eigenes Frontend, ein eigenes Backend und eine eigene Datenbank. Zusätzlich sollte ein gutes Vertikal folgende Eigenschaften haben: 

  • Es sollte self-contained sein. Das bedeutet, dass 90% aller Use-Cases autonom funktionieren, ohne dass sie mit anderen Systemen kommunizieren müssen.  
  • Ein Team “owned” dieses Vertikal und weitestgehend alle Freiheiten mit diesem System zu machen, was sie wollen. Dazu ist es wichtig eine Makroarchitektur festzulegen. 
  • Es gibt keine Shared-Business-Logik. 

Nun ist vermutlich die Frage, wie ein Bestellsystem auf die User zugreifen kann, wenn es nicht auf das Usersystem zu greifen darf. Die Lösung hierfür ist asynchrone Kommunikation und doppelte Datenhaltung. Tools hierfür können Kafka oder Pulsar sein. Immer wenn ein neuer User angelegt wird, wird dies an das Buchhaltungssystem gesendet. Das Buchhaltungssystem speichert sich alle relevanten Informationen in seiner Datenbank und kann jederzeit darauf zugreifen. Der Vorteil ist, dass der Zugriff schnell ist und auch funktioniert, wenn das Usersystem nicht erreichbar ist. Der Nachteil ist, dass die Synchronisierung aus dem Takt geraten kann und man einen Repeat-Mechanismus einbauen muss. 

Im Frontend werden die verschiedenen Systeme dann durch Links oder durch Widgets eingebunden und so haben Enduser einen Systemkomplex, welcher für sie wie ein einzelnes System aussieht. 

Der neu erstellte Messagebroker erhöht die Komplexität der Architektur und erfahrene Architekten sollten einen richtigen Schnitt erstellen. Diese Komplexität sollte aber so abstrahiert werden, dass nur erfahrene Entwickler*innen diese sehen müssen. Unsere Empfehlung und gängige Praxis ist ein*e Entwickler*in aus jedem Team. Alle anderen Akteur*innen sehen nur ihr Vertikal, welches ihr Leben viel einfacher macht, da die sichtbare Komplexität massiv verringert wird. Es ist sehr einfach, in der eigenen Welt Dinge zu ändern und gleichzeitig ist es extrem schwer, gegen die Grundarchitektur zu verstoßen. Andere Vertikale wird man im Bestfall nie sehen. 

Fazit

Für viele Softwaresysteme reicht es vermutlich aus, eine klassische Frontend / Backendarchitektur zu nutzen. Sofern mehrere Teams an der Software arbeiten, sollte jedoch geprüft werden, ob man sein System splittet oder Neuentwicklungen in einem neuen System macht. Am Ende ist es auch ein Tradeoff, der nicht ohne Nachteile daher geht.