Microservices-Albtraum: Wenn aus Evolution Eskalation wird
Woran denken wir als Erstes, wenn wir das Wort "Monolith" hören? Ein großes festes Gebilde. Und wenn Sie nun an einen Monolithen in der Software-Entwicklung denken? Sehr wahrscheinlich sind die Worte "alt", "hohe Kopplung", "groß" und "schlechter Code Style" ebenfalls Worte, die Ihnen direkt in den Kopf schießen. (Sicherlich auch das ein oder andere Schimpfwort.)
Doch in einem sind sich alle einig: Monolithen sind böse!
Deshalb setzen viele heutzutage auf das neue großartige Flaggschiff der Softwareentwicklung: Microservices! Vor einigen Jahren waren sie DER Headliner auf allen Konferenzen und nun, einige Jahre später, schauen wir auf die Ergebnisse dieses Trends.
Es gibt dutzende Kommentare aus der Community hierzu. Ein Kommentar, den ich im Laufe meiner Recherchen gelesen habe, klang ungefähr so: "…Ich arbeite seit mehreren Jahren an diversen Microservice-Projekten und es war jedes Mal dasselbe: Sie sind entwicklerunfreundlich, haben ein Komplexitätslevel über 10.000 und alle Bad Practices wurden benutzt, die man kennt. Jeder war frustriert – Kunden und Entwickler – nur der Architekt grinste." Das ist nur einer von vielen Erfahrungsberichten, die man finden kann.
Aber in einem sind sich (die negativen zumindest) einig: Microservices sind auch böse!
Der folgende Text befasst sich daher mit den "möglichen" Szenarien einer Microservice-Architektur, wie man diese bereits zu Projektbeginn berücksichtigen und einplanen kann und mit alternativen Ansätzen, die im Alltag einen Ausweg bieten können.
Die 5 Albträume der Microservices
Kommen wir nun zu dem spannenden Teil dieses Textes: die eigentliche Herausforderung im Umgang mit Microservices, die Probleme, die auftreten "können". Hierbei ist zu erwähnen, dass mit dem entsprechenden Wissen keiner dieser Albträume eintreten muss und es auf keinen Fall eine Empfehlung ist, keine Microservices zu benutzen.
Wie komme ich überhaupt dazu, darüber zu schreiben? Ganz klar: durch den Projektalltag. Im Projektgeschäft ist es als Software-Architekt:in ganz normal, die technischen Vorstellungen der Kunden zu hören und auch beratend zur Seite zu stehen. Nicht selten fällt dann auch der Satz "Wir möchten Microservices!". Die direkte Frage ist dann natürlich "Warum?". Die Antworten sind mannigfaltig – s. Abb. 1.
Das sind leider keine Aussagen, die eine Microservice-Architektur begründen. Doch lasst uns doch einfach einmal einen Schritt weitergehen und schauen, was passiert, wenn man trotzdem den Weg geht und was dabei alles schiefgehen kann.
Nightmare of… Infrastructure
In einem Szenario, in dem hunderte Microservices im Einsatz sind, ist auch die Infrastructure entsprechend unabhängig in ihrem Deployment, Monitoring, der Anwendung von Security- Richtlinien und Skalierung. Was kann da schon schiefgehen (natürlich gibt es für jedes Problem eine Lösung, doch wer kann schon behaupten, dass alle Eventualitäten immer von vornherein abgefangen werden können)?
- Deployment: Was, wenn ein Deployment der hunderten Services fehlschlägt? Wer wird benachrichtigt? Der, der das Deployment angestoßen hat? Was, wenn diese Person im Urlaub ist?
- Monitoring: Was, wenn keine Fehler gemeldet werden, weil das Monitoring selbst nicht aktiv ist? Ich habe selbst schon erlebt, dass ein kleiner Service über mehrere Wochen nicht funktioniert hat und es keinem aufgefallen ist, weil das Monitoring einfach ausgefallen war. Bei monolithischer Software fällt es auf, wenn die gesamte Seite nichts meldet, aber bei einem kleinen Service?
- Security: Jeder Service muss abgesichert sein – wie kann man sicherstellen, dass alle Services ausreichend sicher implementiert wurden und auch in einem sicheren Netzwerk laufen? Terraform-Templates liefern hier oftmals die Antwort – solange, bis es zu den ersten Ausnahmen kommt (die leider nicht selten sind).
- Skalierung: Der Traum von Microservices, jeder Service kann für sich allein skaliert werden – zu jeder Tageszeit, um das beste Ergebnis zu erhalten (und natürlich Kosten zu sparen!). Doch auch das Skalieren kostet Zeit und muss auf Metriken "reagieren". Im Alltag ist der große Marketing-Hammer "Wir können skalieren." nur eine Entscheidung für ein Pricing Tier, das sich nahezu nie mehr ändert. Nicht, weil sich keiner darum kümmert, sondern weil die Leistung der Server heutzutage einen so hohen Standard erreicht hat und die Programmier-Frameworks immer effizienter werden, dass automatische Skalierung (oder auch manuelle) oftmals nicht mehr notwendig ist. (Ganz nebenbei: Man kann sogar einen Monolithen skalieren.)
Was bedeutet das für eine Architekturentscheidung? Eine Microservice-Architektur muss von vornherein in diesen Punkten abgesichert werden. Wenn ich keine Skalierung benötige und sowohl bei Deployments als auch Monitoring nicht sicher sein kann, dass ich diese Punkte ausreichend für eine große Servicelandschaft abdecken kann, sollte die Frage lauten: Will ich es trotzdem? Oder: Ist es mir wichtig?
Nightmare of… Consistency
Wem das CAP-Theorem bekannt ist, weiß: Konsistenz, Verfügbarkeit und Fehlertoleranz sind drei Säulen, von denen immer nur zwei gleichzeitig erfüllt werden können. Konsistenz ist dabei einer der Punkte, die je nach Business Case relevant sein können oder nicht. Teilweise reicht es, dass Eventual Consistency erreicht wird. Wenn zum Beispiel bei einem Konzert 101 anstatt 100 Karten verkauft wurden und die Grenze eigentlich 150 sind (Brandschutz – wir kennen es), ist es dann wichtig, dass eine Strong Consistency gefordert ist? Das hängt immer von den fachlichen Anforderungen an das System ab.
Wenn wir aber von Eventual Consistency, Distributed Transactions und Error Handling im Bereich von Datenübertragungen sprechen, wird schnell klar: Falls wir diesen Weg gehen, dann müssen wir uns um diese Fälle kümmern. Wenn die Daten nicht Real-Time aktuell sind, wenn Transactions verloren gehen können, muss für diesen Fall das Error Handling dies abfangen. Das kann auch das erneute Senden von Anfragen sein.
In einem API-Approach ist die Übersicht von solchen "Was-wäre-wenn"-Fällen um ein Vielfaches einfacher. In einem Microservice-Ansatz hingegen muss eine zentrale Strategie existieren. Die Nutzung eines Orchestrators, der solche Fälle behandelt, wäre eine Strategie. Denn: Je mehr Unabhängigkeit die Services haben, umso mehr kann unabhängig voneinander schief gehen und zu einer Vielzahl an fehlerhaften Zwischenständen führen.
Nightmare of… Organizational Constraints
In der technischen Blase eines Software-Architekten oder eines Technical Leads ist es einfach, diesen Punkt zu ignorieren. Denn warum sollte es mich in meiner Entscheidung pro oder contra Microservice-Architektur beeinflussen, wie die Organisation des Teams im Hintergrund aussieht? Die Realität stellt leider nicht immer in jedem Projekt 30 Full-Stack-Entwickler zur Verfügung, die alle mit 20 Jahren Berufserfahrung glänzen können. Genau aus diesem Grund ist die Entscheidung der korrekten Software-Architektur auch davon abhängig, wie das gesamte Projekt organisiert werden kann.
Natürlich können Microservices nicht nur mit 30 Full-Stack-Entwicklern mit 20 Jahren Berufserfahrung gestemmt werden, doch je nach gewünschtem Projektstart (O-Ton vieler Kunden "gestern") ist es oftmals auch nicht möglich, eine Microservice-Architektur ausreichend zu planen und wenn das Know-how des Teams genauso verteilt ist wie die geplante Architektur, ist bereits zu Projektstart das Projekt durch einen Bad- Practice-Prozess gelaufen.
Im Idealfall werden Microservices mit einem Microteam umgesetzt. Unabhängige Microservices haben feste Microteams, die die Verantwortung für die Umsetzung ihrer eigenen Services tragen. Das muss neben der Teamgröße auch mit einem entsprechenden Koordinationsaufwand geleistet werden. Genauso, wie ein Orchestrator in einem event-based Microservice-System für Ordnung sorgen kann, muss auch auf der menschlichen Ebene die Entwicklung dieser Services koordiniert werden.
Zusätzlich ist das geforderte Know-how heutzutage in Teams auf einem sehr hohen Standard. Frontend, Backend, Software-Pattern, Ops – etwas mit KI? Egal, ob Junior oder Senior, für ein erfolgreiches Microservice-Projekt muss auch hier eine ausgewogene Verteilung stattfinden können. Wenn nicht, nützt leider auch die am besten geplante Microservice-Architektur niemandem etwas. Bereits auf dieser Ebene muss die Unabhängigkeit zwischen den Teams gewährleistet werden. Wenn ein Projekt schnell gestartet werden soll, muss die Planung entsprechend mit genug Vorlaufzeit angedacht sein. Oftmals ist der Gedanke bei technisch affinen Kunden: "Microservice heißt, wir fangen morgen an und sind noch schneller fertig, denn man kann ja unabhängig arbeiten." Doch die Realität sieht vor, dass die Planung in diesen Projekten aufwendiger ist und somit mehr Vorarbeit geleistet werden muss. Erst dann kann die Skalierbarkeit für Microservices auch auf Teams angewendet werden.
Nightmare of… Requirements
In einem Microservice-Universum bedeutet eine fachliche Grenze auch gleichzeitig eine technische Grenze und umgekehrt. Klein geschnittene Services leben davon, dass sie diese Grenze auch nicht überschreiten müssen. Daher kommen wir nun zu einer kleinen Story-Time von Projekten, bei denen vorab keine Microservices eingesetzt wurden (aus vielen diversen Gründen), was an dieser Stelle eine gute Entscheidung war und zum Erfolg geführt hat.
Fachliche Grenzen – der Teufel liegt im Detail
In einem B2B-Shop gibt es Preise und Verfügbarkeiten für ein Produkt. Beide Datensätze sind unabhängig voneinander zu Beginn des Projektes. Solange, bis eine Anforderung an das System gestellt wurde, die besagte, dass für ein Produkt in einem bestimmten Status kein Preis mehr angezeigt werden darf, außer es gibt Restbestand. Die initiale fachliche Grenze wurde aufgebrochen und die nun technische Grenze musste neu überdacht werden. In einem Microservice-Universum wäre diese Anpassung um ein Vielfaches größer und umständlicher gewesen. Neue Services hätten etabliert werden müssen, neue Grenzen gesetzt, womöglich hätte man Services zu einem Midi-Service verschmolzen? In unserem Fall haben wir unsere fachlich abgegrenzten Bibliotheken "nur" umgebaut und die Infrastruktur blieb unangetastet.
Integrationsproblem – Unser Security-Team erlaubt das nicht
Geplant sind in einem System zwei kleine Komponenten: Eine Komponente ist für die Zulieferung von Daten aus einer Datenpipeline zuständig und eine weitere für die Verarbeitung. Beide laufen zu festgelegten Zeitpunkten, um keine Abhängigkeiten zu haben. Wenn die Verarbeitung keine neuen Daten findet, wartet sie bis zum nächsten festgelegten Zeitpunkt.
Zum Projektende wurde aber von der Security die Interaktion zwischen den beiden Services verboten. Die Folge: manuelle Uploads in eine Datenbank, Formatprobleme durch menschliche Fehler beim Upload, doppelte Uploads, "irgendwann" Laufenlassen der Verarbeitung. Und daraus wiederum die Frage: "Wann weiß ich, dass meine Daten korrekt angekommen sind?" Ein Microservice zur Verarbeitung von Daten (ich mache aus Datensatz A einen Datensatz B) wird zu einem Midi-Service, der weitere Verantwortlichkeiten trägt oder der Prozess muss entsprechend aufgebläht/erweitert werden mit weiteren Microservices.
Third-Party-Systeme – wenn SAP, Google Analytics und andere Drittsysteme mitspielen wollen
Jeder, der schon einmal mit SAP gearbeitet hat, weiß: Es ist eine standardisierte Software, die von vielen Großkonzernen weltweit eingesetzt wird. Alle Prozesse sind klar strukturiert und alle Tabellen und Spalten folgen einer vordefinierten, wohl bekannten Aufteilung. Und jeder, der bei diesem Satz nicht zumindest ein leichtes Lächeln im Mundwinkel zeigte, weiß noch nicht, wie die Realität aussieht. Custom-Tabellen und Custom-Spalten sind eine gern genutzte Variante, um die Realität in SAP abzubilden und daher ist es nicht verwunderlich, dass häufig in Meetings die Floskel "... aber in SAP…" fällt. Dies habe ich leider auch schon zu oft gehört und nicht selten folgen nach solchen Meetings Kopfschmerzen. Und das schon, ohne darüber nachdenken zu müssen, wie man seine Microservices nun schneiden kann, um noch immer in das Konzept zu "passen".
Nightmare of... Independency
Damit kommen wir auch zu dem eigentlichen Albtraum in der Welt der Microservices: Unabhängigkeit. Die Unabhängigkeit von Teams, Technologien, Infrastruktur und Anforderungen ist der Traum, den Microservices bringen sollen… und können!
Wie in jedem Albtraum kann man alles zum Guten wenden und das Projekt in einen Traum verwandeln, wenn man weiß, was einen erwartet und wie man es überwinden kann. Es gibt kein Gut oder Böse, weder bei Monolithen noch bei Microservices. Es gibt nur viele Punkte, die zu beachten sind und die Einflüsse auf die Architektur-Entscheidungen nehmen müssen und sollten. Was habe ich zu erwarten? Welche Abhängigkeiten habe ich? Was-wäre-wenn?
Alternativen zu Microservices
Nicht immer ist es der Monolith oder der Microservice, der das Mittel zur Wahl sein sollte. Nicht immer ist die Wahl der Architektur davon abhängig, was "cool" ist oder was sich ein technischer Lead wünscht.
Natürlich ist auch zu sagen, dass eine Microservice-Architektur in einem vorzugsweise großen Rahmen auch eine sehr gut gewählte Architektur sein kann, die leicht zu skalieren, updaten und verwalten ist. Unabhängige Teams können schnell und effizient kleine Services aufziehen und wenn jemand nun denkt: Albträume? Du erzählst mir nichts Neues! Das weiß ich schon alles und ich kann in meiner Architektur alles abfangen – dann hast Du alles richtig gemacht und der Architekt ist nicht mehr der Einzige in Deinem Team, der grinst, wenn Microservices zum Einsatz kommen.
Aber oftmals sind auch andere Ansätze zu betrachten. Midi-Services? Service-oriented Architecture mit einem Service Bus, der z. B. auch Authentication und Authorization übernimmt? Distributed Monoliths? API-based Approach? Es gibt eine Vielzahl von möglichen Patterns (was wären wir auch, wenn wir uns nicht immer wieder neue Patterns ausdenken würden?).
Aber was hält uns davon ab, einen Hybriden zu schaffen? Das ist ein Ansatz, den ich persönlich aktuell sehr gerne verfolge, um sowohl Microservice-Kenner und -Enthusiasten als auch alte Hasen in ein Boot zu bekommen und den Ausweg aus dem Microservice-Albtraum zu finden. In vielen Projekten ist nur ein Teil der fachlichen Grenzen einzuhalten, wieso also nicht auch nur einen Teil als Microservice umsetzen und den Rest als etwas anderes? Bei kleineren Projekten kann "etwas anderes" sogar ein Monolith sein. Die Software-Architektur muss nicht zu einem strengen Pattern passen, sondern einem Konzept folgen, das alle realistischen Eventualitäten abfangen kann, flexibel ist und von jedem einfach gelebt werden kann. Frei nach dem KISS-Prinzip – Keep it simple and stupid.