Nachdem man sich die Frage gestellt hat, warum man Microservices einsetzen will, und diese richtig beantwortet hat – nicht wegen des Hypes oder um als spannendes Unternehmen für Fachkräfte dazustehen –, geht es im nächsten Schritt natürlich darum, Microservices auch einzuführen. Dabei wird man auf jeden Fall auf einige Probleme stoßen. Es gilt, die Zahl der unknown Unknowns zu reduzieren, sodass Sie in Ihren Projekten weniger Überraschungen erleben. Die Probleme teilen sich in vier Bereiche auf: Technik, Prozesse, Schnitt und Menschen.
Ein Problem auf dem Microservices-Weg ist die Auswahl der Technik. Da die meisten Leute in Microservices-Projekten Techniker sind, brauche ich meiner Erfahrung nach darauf aber nicht wirklich eingehen. Denn was Techniker am liebsten machen, ist, Probleme mit Technik zu lösen. Die Aufgabe des Architekten, Managers oder Mentors besteht aber darin, zu erkennen, wann ein Problem gar nicht technisch bedingt ist, sondern beispielsweise durch einen falschen Schnitt, unpassende Prozesse oder Menscheln erst verursacht wird. Menscheln ist ein Ausdruck, den ich von einem meiner ehemaligen Projektleiter übernommen habe.
Er benutzte diesen Begriff immer dann, wenn Konflikte auf emotionaler und persönlicher Ebene ausgetragen wurden, anstatt sachlich an die Lösung zu gehen. Als Menschen neigen wir dazu, unsere Gefühle gegenüber anderen Menschen in die Bewertung dieser Menschen und ihrer Vorschläge einzubeziehen. In diesen Fällen werden sachlich gute Vorschläge häufig aus emotionalen Gründen abgelehnt. Mit Innovation Tokens haben sie ein Hilfsmittel an der Hand, mit dem man dem Thema Technologieüberflutung begegnen kann. Die Idee von Innovation Tokens besteht darin, dass ein Team für die Umsetzung einer bestimmten Anforderung nur eine begrenzte Anzahl an Tokens zur Verfügung hat. Die genaue Anzahl hängt von den Fähigkeiten und der Einsatzbereitschaft des Teams und der Organisation ab. Das begrenzt die Anzahl neuer Technologien und zwingt zur Priorisierung [1].
Passen die Prozesse überhaupt?
Stellen Sie sich die Frage „Sind die Prozesse meiner Organisation bereit für Microservices?“? Die nächste Frage wäre „Wie kann ich herausfinden, an welchen Stellen die Prozesse noch haken?“.
Um genau diese Antworten zu bekommen, starte ich in meinen Projekten zu Beginn einen Testballon. Der Testballon ist eine kleine „Hello World“-Anwendung, die die bestehenden Makroarchitektur-Entscheidungen[2] umsetzt, aber keinen Termindruck aus der Fachabteilung hat. Das Ziel ist, diese Anwendung mit dem höchstmöglichen Automatisierungsgrad in Produktion zu bringen. Im Endausbau wäre dies Continuous Delivery, d.h. Änderungen könnten vollautomatisiert in der Produktionsumgebung deployed werden. Das muss nicht in jedem Fall erreicht werden, aber wir wollen so nah wie möglich herankommen. Beim Testballon durchläuft die Anwendung meist den bisher im Unternehmen üblichen Deployment-Prozess, wodurch wir auf Hindernisse und Bremsklötze aufmerksam werden und sie Schritt für Schritt aus dem Weg räumen können. Welche Vorteile Continuous Delivery für Microservices mit sich bringen und warum dies sogar Risiken reduziert, erläutert mein Kollege, Eberhard Wolff in seiner Artikelserie „Microservices im Zusammenspiel mit Continuous Delivery"[3].
Einer der wichtigsten Gründe für eine vollständige Automatisierung ist die Reproduzierbarkeit von Deployments und Umgebungen. Wenn wir das Deployment inklusive der Konfiguration der Software und Infrastruktur vollständig automatisieren, verhindern wir individuelle, manuelle Fehler und erhöhen die Zuverlässigkeit unserer Lieferungen. Und wenn wir uns auf das Deployment von Infrastruktur und Applikationen verlassen können, haben wir den Kopf frei, uns auf die wirklich wichtigen Dinge zu konzentrieren: Die fachlichen Features. Dann können wir auch mit manuellen Schritten im Auslieferungsprozess umgehen. Denn manuelle SignOffs sind dann nur noch Klicks auf Buttons in der Deployment-Pipeline. Alles dahinter ist zuverlässig automatisiert.
Was packe ich in meinen Microservice?
Zum Thema Domänenarchitektur gibt es mittlerweile sehr viel Literatur und diverse Vorträge. Im Grunde sind wir uns über die Ziele vermutlich einig: Die Komponenten sollen so geschnitten sein, dass Features so schnell wie möglich in Produktion gehen. Ein Feature oder eine Änderung soll nur einen Microservice betreffen, damit das daran arbeitende Team seine Features unabhängig von anderen Teams entwickeln und liefern kann. So blockiert ein Feature im einen Microservice kein Feature in einem anderen Microservice. Der Fokus liegt auf Teamautonomie.
Außerdem sollte die Stabilität des Gesamtsystems stets gewahrt bleibt. Ein Ausfall eines Microservice soll maximal den Ausfall einer Teilfunktion zur Folge haben, niemals den Ausfall des Gesamtsystems. Dementsprechend schneiden wir so, dass Microservices im Betrieb unabhängig voneinander weiterarbeiten können.
Der Schnitt sollte auch keine technischen Abhängigkeiten schaffen, wo es keine fachlichen Abhängigkeiten gibt. Ein klassisches Beispiel für solche technischen Abhängigkeiten ohne fachliche Notwendigkeit ist ein kanonisches Datenmodell. Während der Fachbereich Logistik sich beispielsweise für die Lieferadresse eines Kunden interessiert, aber nicht für dessen Bestellhistorie, Produktbewertungen oder zuletzt angesehen Produkte, sind diese Daten für den Fachbereich Recommendations äußerst relevant. Beide Bereiche haben eine unterschiedliche fachliche Sicht auf den Kunden. Diese Sichten werden im Domain-driven Design auch als Bounded Contexts bezeichnet. Aus fachlicher Sicht gibt es also nicht den Kunden mit allen Attributen, die in einem Kunden-Microservice verwaltet werden. Stattdessen haben wir die Freiheit, dem Kunden im Logistik-Microservice andere Attribute zu geben als im Recommendations-Microservice. Deshalb sollten wir keinen Kunden-Microservice bauen, denn dadurch entstehen technische Abhängigkeiten der beiden anderen Microservices auf diesen Service, die fachlich nicht notwendig sind. Die Herausforderung, dass alle Services unsere Kunden auseinanderhalten können, haben wir bereits gelöst, indem wir fachliche Schlüssel eingeführt haben, die heute vielen Menschen als Kundennummer bekannt sind. Stefan Tilkov erläutert hier [4], warum ein kanonisches Datenmodell schon bei SOA nicht funktioniert hat und auch für Microservices nichts Gutes bringt.
Auch Lastspitzen sollten sich schnell und effizient abfangen lassen. Wenn wir schon absehen können, dass bestimmte Funktionen, z. B. die Produktsuche, in bestimmten Produktionssituationen deutlich mehr Last bekommen werden als andere Funktionen, wollen wir diese Funktionen möglichst unabhängig voneinander skalieren können. Das bedeutet, ich möchte beispielsweise acht Instanzen von meinem Produktsuche-Microservice starten, während nur drei Instanzen der Benutzerregistrierung laufen. Dieses Ziel erfordert stetiges Abwägen. Denn auf der einen Seite ist es effizient, nur so wenige Instanzen eines Microservice wie eben nötig starten zu können. Auf der anderen Seite kann es, abhängig vom Ressourcenoverhead hinweg (CPU, Memory, Infrastruktur oder Monitoring über alle Dev-, Test- und Produktionsumgebungen), jedoch ineffizient sein, bestimmte Funktionen in einzelne Microservices oder eigene Deployment-Units aufzuteilen.
Beim Testing gilt es, dass Ende-zu-Ende-Tests erst gar nicht oder so kurz wie möglich durchgeführt werden sollten. Wenn die Software aus mehreren sich aufrufenden Microservices besteht, entsteht durch diese so genannte Request-Kaskade eine enge Kopplung. Meist wird dieser Kopplung mit Hardening Phases begegnet. In diesen häufig mehrwöchigen Phasen werden alle Microservices in einer bestimmten Version in der gleichen Testumgebung installiert. Wir sprechen hier vom „Fade-in for end-to-end Testing“ und meinen damit, dass die Deployment-Pipelines der eigentlich unabhängigen Microservices hier auf die Taktung der Ende-zu-Ende-Tests heruntergebremst werden. In dieser Phase kommt die Softwarelieferung quasi vollständig zum Stillstand und ich höre Sätze wie „Wir können diese neue Version nicht deployen, der Ende-zu-Ende-Test läuft gerade. Dafür brauchen wir eine weitere Integrationstestumgebung“. Ein anderer Satz, den ich schmerzhaft fand war: „Wir können jetzt nicht deployen, weil wir noch darauf warten, dass das Testteam die Tests an dem anderen Projekt beendet und Zeit für uns hat“. Aus meiner Sicht ist es unsere Aufgabe, diesen Problemen zu begegnen und den Drang zu solchen Ende-zu-Ende-Testphasen zu unterdrücken. Denn ich stimme James Lewis hier komplett zu, wenn er zitiert „End-to-end testing can be referred to as risk management theatre“[5]. Durch Ende-zu-Ende-Tests erzeugen wir Queues, in denen neue Versionen unserer Microservices gesammelt werden, bevor wir sie irgendwo installieren, um sie zusammen zu testen. Dieses Queueing sorgt für eine Verzögerung der Cycle Time, also der Liefergeschwindigkeit, und erhöht dadurch wiederum nicht nur die Zeit, bis ein Feature in Produktion geht, sondern auch die Zeit, bis ein Bug gefunden wird. Und wie wir aus Büchern zu Product Development (z. B. FLOW [6]) und Continuous Delivery wissen, haben solche Warteschlangen so viele Nachteile, dass wir gut daran tun, sie zu vermeiden.
Die Phase des Ende-zu-Ende-Testen können wir einerseits verkürzen, indem wir die Request-Kaskaden, also Aufrufe zwischen Microservices, auf ein Minimum reduzieren und auch keinen Distributed-Monolith[7] bauen. Dies sollten wir als Architekturprinzip in unsere Makroarchitektur-Entscheidungen aufnehmen, damit die Konsequenzen der Missachtung allen Projektteilnehmern klar sind.
Ein anderer Ansatz ist, die bekannten Schnittstellen zwischen Microservices in expliziten Verträgen auszudrücken und diese bereits im Build von „Consumer Driven Contract“-Tests validieren zu lassen. In Consumer-driven Contracts beschreibt der Aufrufer (Consumer) einer Schnittstelle resp. dessen Team, was an die Schnittstelle geschickt wird (Request) und welche Werte in der Antwort (Response) zwingend erwartet werden. So weiß das Team des Provider-Services jederzeit, welcher Consumer welche Attribute der Response zwingend benötigt und welche nicht. Das gibt ihm die Freiheit, nicht benötigte Felder zu entfernen und so die Schnittstelle weiterzuentwickeln. Da die Consumer-Driven Contract-Tests in die Deployment-Pipeline des Providers aufgenommen werden, erkennt das Team auch frühzeitig, wenn eine Änderung an seinem Microservice den Schnittstellenvertrag zu einem anderen Microservice verletzt, bevor die Änderung überhaupt in einer Testumgebung deployed wurde. Mein Kollege Michael Vitz hat zu Consumer-Driven Contracts einen einführenden Beitrag in seiner Kolumne verfasst. [8]
Tipps für den Serviceschnitt
Zunächst sollte man grobe Services schneiden und den Schnitt dann nach und nach verfeinern, wenn es sich als notwendig erweist.
Ob mein Schnitt richtig ist, validiere ich anhand von Use Cases. Das bedeutet, wir zeichnen einen Schnitt auf und nehmen uns dann einige der Kern-Use-Cases und spielen diese durch mit dem Fokus darauf, dass ein Use Case möglichst immer von einem Microservice allein behandelt werden kann. Wir betrachten dann, wohin die Requests gehen und ob dem Microservice alle notwendigen Daten vorliegen. Falls nicht, stellt sich die Frage, ob wir die Daten von anderen Microservices replizieren können. Ob diese Replikation im Hintergrund laufen kann oder dieser UseCase so hohe Datenkonsistenzanforderungen [9] hat, dass wir doch synchron auf den anderen Microservice zugreifen müssen.
Menschen: Produktdenken statt Projektdenken
Schauen wir uns eine typische Firma an, finden wir fast immer funktionale Silos: Vertrieb, Marketing, Finance, HR, Projektmanagement, Softwareentwicklung, Testing, Betrieb und manchmal eine Architekturabteilung. Wenn nun eine Abteilung eine neue Produktidee hat, beginnt die Kommunikation zwischen den Abteilungen um ein Projekt zur Realisierung der Idee durchzuführen.
Projekte sind nach einem definierten Ziel beendet. Was danach mit dem erzeugten Produkt passiert, liegt meist nicht im Fokus des Projektteams. Da einige Architekturentscheidungen mit einer kurzfristigen Betrachtung gut, mit einer langfristigen Betrachtung aber sehr negativ sein können (vergleiche „Request-Kaskaden“ oben), ist es aus meiner Sicht sinnvoll, Teams stattdessen um Produkte zu gruppieren. Diese Teams haben die Aufgabe, das Produkt langfristig weiterzuentwickeln.
Es gibt noch einen anderen Grund, warum das Überdenken der Zusammenarbeit relevant ist. Das Gesetz von Conway[10] besagt
„Organisationen, die Systeme entwerfen, […] sind auf Entwürfe festgelegt, welche die Kommunikationsstrukturen dieser Organisationen abbilden.“
Das bedeutet, die Architektur unserer Systeme wird sich im Laufe der Zeit den Kommunikationsstrukturen der beteiligten Personen anpassen. Diese Aussage wurde mittlerweile vom Harvard Business School anhand einer Studie [11] bestätigt. Konkret bedeutet dies, wenn wir im Unternehmen Teams mit Datenbank-Spezialisten und UI-Spezialisten haben, werden wir sich unserer Software auch getrennte Module für Datenbank und UI haben. Unser Ziel aber, weg von technischen Modulen hin zu fachlichen Modulen zu kommen.
In Conwways Gesetz steckt zugleich eine Warnung wie eine Chance. Wir müssen die Kommunikationsstrukturen unserer Teams beachten und diese aktiv formen.
Cross-Funktionale Teams funktionsfähig machen
Ein erster Schritt ist, sogenannte crossfunktionale Teams aufzusetzen, in denen Vertreter aller Unternehmensbereiche sind. Das schließt neben den klassischen Software-Bereichen wie Entwicklung, Test und Betrieb auch Marketing, Vertrieb und Finance. Ziel ist es, alle notwendigen Kompetenzen für die Entwicklung eines Produktes im Team vertreten zu haben. So ist das Team in der Lage unabhängig von anderen schnelle Entscheidungen zu treffen. Dadurch vermeiden wir viele Verzögerungen, die entstehen, wenn Personen außerhalb des Teams in eine Aufgabe oder Entscheidung einbezogen werden müssen. Auch hier ist wieder die o.g. Team-Autonomie ein hohes Gut.
„Wenn ich die Leute aufteile, dann verliere ich doch die hohe Expertise, die solche Technik-fokussierten Teams im Laufe der Zeit aufbauen. Muss ich dann auf diese Synergie-Effekte verzichten?“ Das ist mehr oder weniger die Frage, ob die Firma auf „Wertschöpfung“ oder „Kostenreduzierung“ ausgerichtet ist. Wenn es mir wichtig ist, mein Produkt nach vorne zu bringen und schnell auf Kunden und den Markt reagieren zu können, dann sind solche Cross-funktionalen Teams der Schlüssel dazu. Diese Teams werden dafür eine stärkere Expertise in der Fachlichkeit aufbauen, die letztlich wieder dem Produkt zu Gute kommt. Das Team identifiziert sich mit seinem Produkt und wird deshalb seine Entscheidungen automatisch auf den langfristigen Erfolg des Produktes ausrichten.
Ein konkretes Alltagsbeispiel verdeutlicht diese Herangehensweise wahrscheinlich: Stellen Sie sich einen Operationssaal vor. Wir finden hier Vertreter unterschiedlicher spezialisierter Gruppen (vielleicht sogar Abteilungen) wie Chirurgen, Anästhesisten, Schwestern oder Laborpersonal. Alle haben ein gemeinsames Ziel - wenn man so will die fachliche Anforderung: Das Herz dieses Patienten ist schwach. Er wird sterben, wenn wir ihm kein neues Herz einsetzen! Alle bringen ihre Expertise und Erfahrung ein für das Wohl des Patienten. Im Operationssaal, das wissen wir, entscheiden oft Sekunden über Leben und Tod. Deshalb sind alle relevanten Personen anwesend um ihren Teil beizutragen. Sie arbeiten gleichzeitig Hand in Hand.
Conways Law nutzen
Wie können wir nun Conways Gesetz für uns nutzen? Es gibt Studien, die belegen, dass die räumliche Distanz zwischen Menschen bestimmt, wie oft sie über zwischenmenschliche Themen sprechen. Und diese wiederum schafft Bindungen und Vertrauen, was ein wichtiger Faktor für gute Teamarbeit ist. So simpel wie es klingen mag, der Schlüssel ist anscheinend wirklich, dass die Leute sich kennenlernen indem sie Zeit miteinander verbringen. Das kann bedeuten, dass sie im gleichen Büro sitzen, zusammen in die Pause gehen oder aber Freizeitaktivitäten miteinander teilen. Passiert dies nicht automatisch, können wir auch nachhelfen: Hier zu sind Team-Buildings Maßnahmen, Ganztages-Workshops oder auch längere Brainstorming-Sessions gut geeignet.
Manchmal stoßen wir aber auf Situationen, da uns die Vergangenheit ausbremst. Gerade wenn vorher die strikte Trennung der Abteilungen gelebt und Vorurteile gegenüber „den anderen“ geschürt wurden, die „jetzt Teil meines Teams sein sollen?“, müssen wir aktiv werden, Grabenkämpfe beizulegen und alte Gräben zuzuschütten. Konkret müssen wir das verlorene Vertrauen wieder aufbauen, was ein langwieriger aber lohnenswerter Prozess sein kann. Hierbei hilft es beispielsweise, wenn die Personen wieder anfangen, die Menschen hinter den Vorurteilen zu sehen und Gemeinsamkeiten mit ihnen entdecken. Dies sind Punkte, die in einer Team Building Session angegangen werden können. Ignorieren wir diesen Vertrauensverlust jedoch, kann es sehr gut sein, dass das Team nie wirklich als Team zusammenarbeitet. Das äußert sich z.B. dadurch, dass Teamentscheidungen von einzelnen nicht respektiert werden oder die Teammitglieder nicht füreinander einstehen, sondern Schuldzuweisungen stattfinden. Dies sollten Alarmglocken für uns sein.
Ein Team, ein gemeinsames Ziel
Es erübrigt sich wahrscheinlich zu sagen, dass ein Team natürlich auch ein gemeinsames Ziel benötigt. Nichts ist schädlicher, als wenn ein Team unterschiedlichen Managern folgen muss, die konkurrierende Ziele ausgeben. Dies habe ich beispielsweise erlebt, wenn einzelne Teammitglieder noch in ihren Abteilungsstrukturen verhaftet bleiben und dementsprechend der Abteilungsleiter noch Weisungen erteilt. Im Optimalfall hat ein Team nur einen weisungsbefugten Manager, der keine mit einander in Konflikt stehenden Ziele ausgibt. Dieser Manager sollte auf dem Feature-Team sitzen, nicht auf Services oder Komponenten.
Fazit
Wir haben in diesem Artikel verschiedene Bereiche kennengelernt, in denen bei der Einführung von Microservices teils unerwartete Herausforderungen auf uns warten. Bei vielen Herausforderungen konnten wir nur an der Oberfläche kratzen, so dass Sie einen Überblick bekommen und in ihrem Projekt nicht unvorbereitet erwischt werden. Aus meiner Sicht sind Microservices ein hervorragendes Mittel um den sich immer schneller ändernden Marktverhältnissen auf organisatorischer und technischer Ebene begegnen zu können. In diesem Sinne wünsche ich Ihnen viel Erfolg bei der Einführung. Bei Fragen, Feedback oder Anregungen dürfen Sie auch gerne auf mich zukommen.
Ich möchte an dieser Stelle meinen KollegInnen Philipp Haußleiter, Falk Hoppe, Sven Johann, Claudia Rauch, Tammo van Lessen, Michael Vitz und Eberhard Wolff für den Gedankenaustausch und das Feedback zu einer frühen Version des Artikels danken.
Literatur & Links
-
Alexander Heusingfeld, „Innovation Tokens: Gegen Informatikerromantik und Technologieüberflutung“ von Alexander Heusingfeld – https://www.innoq.com/de/articles/2017/06/innovation–tokens/ ↩
-
Makroarchitektur: Alexander Heusingfeld, „Deployment und Monitoring von Microservices“ – https://www.innoq.com/de/articles/2016/10/microservices–deployment–und–monitoring/ ↩
-
Eberhard Wolff, „Microservices im Zusammenspiel mit Continuous Delivery“ – http://www.heise.de/developer/artikel/Microservices–im–Zusammenspiel–mit–Continuous–Delivery–Teil–1–die–Theorie–2376386.html ↩
-
Jan Sternberg about Stefan Tilkov’s „Avoid Canonical Data Models“ https://www.infoq.com/news/2015/04/canonical–data–models ↩
-
James Lewis, „Microservices and the Inverse Conway manoeuver“ – https://www.youtube.com/watch?v=uamh7xppO3E ↩
-
“FLOW: The Principles of Product Development”, Donald G. Reinertsen ↩
-
Netflix' Ben Christensen, „Microservices Ending up as a Distributed Monolith“ – https://www.infoq.com/news/2016/02/services–distributed–monolith ↩
-
Michael Vitz, „Consumer–Driven Contracts – Testen von Schnittstellen innerhalb einer Microservices–Architektur“ – https://www.innoq.com/de/articles/2016/09/consumer–driven–contracts/ ↩
-
Datenkonsistenzanforderungen: Eberhard Wolff, „Datenarchitekturen nicht nur für Microservices“ – https://entwickler.de/leseproben/datenarchitekturen–579777442.html ↩
-
Wikipedia „Gesetz von Conway“ https://de.wikipedia.org/wiki/GesetzvonConway ↩
-
Harvard Business School „Exploring the Duality Between Product and Organizational Architectures: A Test of the ‚Mirroring‘ Hypothesis“ – http://www.hbs.edu/faculty/Pages/item.aspx?num=43260 ↩