Tipps & Tricks

Probleme bei der Einführung von Microservices: Seien Sie vorbereitet!

Alexander Heusingfeld

© Shutterstock / pulsar01

Nachdem man sich die Frage gestellt hat, warum man Microservices einsetzen will, und diese richtig beantwortet hat – nicht wegen dem Hype oder um als sexy 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.

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, welche die bestehenden Makroarchitekturentscheidungen 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 deployt werden. Das muss nicht in jedem Fall erreicht werden, aber wir wollen so nah wie möglich rankommen. 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 bringt und warum dies sogar Risiken reduziert, erläutert mein Kollege Eberhard Wolff in seiner Artikelserie „Microservices im Zusammenspiel mit Continuous Delivery“.

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, um uns auf die wirklich wichtigen Dinge zu konzentrieren: die fachlichen Features. Dann können wir auch mit manuellen Schritten im Auslieferungsprozess umgehen. Denn manuelle Sign-offs 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, warum ein kanonisches Datenmodell schon bei SOA nicht funktioniert hat und auch für Microservices nichts Gutes bringt.

W-JAX
Mike Wiesner

Data- und Event-driven Microservices mit Apache Kafka

mit Mike Wiesner (MHP – A Porsche Company)

Niko Köbler

Digitization Solutions – a new Breed of Software

with Uwe Friedrichsen (codecentric AG)

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“. 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 [1]) und Continuous Delivery wissen, haben solche Warteschlangen so viele Nachteile, dass wir gut daran tun, sie zu vermeiden.

Die Phase des Ende-zu-Ende-Testens können wir einerseits verkürzen, indem wir die Request-Kaskaden, also Aufrufe zwischen Microservices, auf ein Minimum reduzieren und auch keinen Distributed Monolith bauen. Dies sollten wir als Architekturprinzip in unsere Makroarchitekturentscheidungen 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 respektive dessen Team, was an die Schnittstelle geschickt wird (Request) und welche Werte in der Antwort (Response) zwingend erwartet werden. So weiß das Team des Providerservice 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 deployt wurde. Mein Kollege Michael Vitz hat zu Consumer-driven Contracts einen einführenden Beitrag verfasst.

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 sie 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 Use Case so hohe Datenkonsistenzanforderungen 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, 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 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 von der Harvard Business School anhand einer Studie bestätigt. Konkret bedeutet dies, wenn wir im Unternehmen Teams mit Datenbankspezialisten und UI-Spezialisten haben, werden wir für unsere Software auch getrennte Module für Datenbank und UI haben. Unser Ziel ist aber, weg von technischen Modulen hin zu fachlichen Modulen zu kommen. In Conways Gesetz stecken zugleich eine Warnung und eine Chance. Wir müssen die Kommunikationsstrukturen unserer Teams beachten und diese aktiv formen.

Ein erster Schritt ist, sogenannte crossfunktionale Teams aufzusetzen, in denen Vertreter aller Unternehmensbereiche sind. Das schließt neben den klassischen Softwarebereichen wie Entwicklung, Test und Betrieb auch Marketing, Vertrieb und Finance ein. Ziel ist es, alle notwendigen Kompetenzen für die Entwicklung eines Produkts 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 Teamautonomie ein hohes Gut.

„Wenn ich die Leute aufteile, dann verliere ich doch die hohe Expertise, die solche technikfokussierten Teams im Laufe der Zeit aufbauen. Muss ich dann auf diese Synergieeffekte verzichten?“ Das ist mehr oder weniger eine 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 crossfunktionalen Teams der Schlüssel dazu. Diese Teams werden dafür eine stärkere Expertise in der Fachlichkeit aufbauen, die letztlich wieder dem Produkt zugute kommt. Das Team identifiziert sich mit seinem Produkt und wird deshalb seine Entscheidungen automatisch auf den langfristigen Erfolg des Produkts ausrichten.

Ein konkretes Alltagsbeispiel verdeutlicht diese Herangehensweise: Stellen Sie sich einen Operationssaal vor. Wir finden hier Vertreter unterschiedlicher spezialisierter Gruppen oder vielleicht auch 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 für das Wohl des Patienten ein. 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.

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: Hierzu sind Team-Building-Maßnahmen, Ganztagesworkshops oder auch längere Brainstormingsessions gut geeignet.

Manchmal stoßen wir aber auf Situationen, bei denen 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 eines 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. Das kann ein langwieriger, aber lohnenswerter Prozess sein. 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.

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 miteinander in Konflikt stehenden Ziele ausgibt. Dieser Manager sollte auf dem Featureteam 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 Probleme auf uns warten. Bei vielen Problemen konnten wir nur an der Oberfläche kratzen, sodass 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 Hausleiter, 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.

Geschrieben von
Alexander Heusingfeld
Alexander Heusingfeld
Alexander Heusingfeld ist Senior Consultant bei innoQ. Als Berater, Entwickler und Architekt unterstützt er Kunden vor allem mit seinen langjährigen Kenntnissen von Java- und JVM-basierten Systemen. Meist beschäftigt er sich hier mit dem Design, der Evaluierung und Implementierung von Architekturen für Enterprise Application Integration (EAI), modernen Webanwendungen und Microservices. Twitter: @goldstift
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.