Microservices und ihre Alternativen
Risiken …
Genau an dieser Stelle bemerkt man erneut eine der grösseren Herausforderungen beim Thema Microservices: Wie gross ist Micro eigentlich, und was ist der ideale Serviceschnitt? Je feiner die Granularität, desto einfacher ist es, die Logik wiederzuverwenden, die in den Diensten gebunden ist, und desto verständlicher sowie austauschbarer sind die Dienste. Das sind alles Vorteile. Nachteilig ist aber, dass mit steigender Granularität auch die externe Kommunikation steigt. Mit externer Kommunikation ist hierbei all der Informationsaustausch gemeint, der ausserhalb des betrachteten Bestandteils beziehungsweise Dienstes geschieht.
Diese Kommunikation ist aber vergleichsweise langsam und kann im Rahmen von Cloud-Software auch Traffic-Kosten verursachen. Während man innerhalb eines Prozesses, wie es bei einem Monolithen üblich ist, keinerlei Kommunikationskosten zu tragen hat, entstehen bei der Kommunikation innerhalb eines Rechenzentrums oder über Grenzen von Rechenzentren hinweg entsprechende Traffic-Kosten. Neben diesen deutlich wahrnehmbaren Kosten fällt auch auf, dass es viel schwieriger ist, ein Gefühl für das Gesamtbild der Software zu erhalten. Darüber hinaus ist sie vergleichsweise schwer zu analysieren. Im Fehlerfall kann man also nicht unbedingt einfach einen Debugger anwerfen und dann den Status der interagierenden Dienste auslesen.
Um dies zu verdeutlichen, wird in Bild 5 ein beispielhaftes Service-Mesh gezeigt. Diese Netze von Diensten zeigen an, welche Dienste mit welchen anderen kommunizieren, und verdeutlichen somit die Laufzeitabhängigkeiten der Dienste untereinander. Je mehr Dienste es gibt, desto komplexer ist das so entstehende Netz, und diese Netze können durchaus sensibel auf Störungen reagieren.
Möchte in Bild 5 beispielsweise Service A eine Anfrage bearbeiten und benötigt er dazu Zuarbeiten von Service B, kann sich eine transitive Abhängigkeit bis hin zu Service X ergeben, je nachdem, wie die einzelnen Dienste umgesetzt sind. Diese Abhängigkeit wird aber erst ersichtlich, wenn man die Dienste gemeinsam testet. Prüft man jeden für sich und ersetzt die externen Abhängigkeiten durch Testdoubles, werden solche Zusammenhänge nicht auffallen.
Weiterhin kann man Bild 5 auch entnehmen, dass der Dienst B wie eine Spinne im Netz sitzt. In seine Richtung gehen viele Anfragen und von ihm werden viele Anfragen gestellt. Fällt dieser Dienst aus, könnte das gesamte Netz und damit auch das Softwaresystem nicht mehr korrekt arbeiten. In Summe betrachtet hat man also aufgrund einiger ungünstiger Entscheidungen ebenfalls eine stark gekoppelte Struktur, nur dass diese nun auch noch verteilt vorliegt.
Die Einfachheit der einzelnen Dienste wird somit durch die Komplexität ihrer Gesamtkomposition wettgemacht. Dem kann man entgegenwirken, indem man die Dienste im Betrieb kontinuierlich überwacht und möglichst schnell auf Fehler reagiert. Netflix geht hierbei sogar so weit, dass sie über ihr Tool Chaosmonkey Server in der Produktionsumgebung vom Netz nehmen und prüfen, wie das Gesamtsystem darauf reagiert. Dies ist ein beeindruckendes Vorgehen, zugleich ist es aber auch mit erheblichen Betriebskosten verbunden, die von kleineren Unternehmen höchstwahrscheinlich nur schwer getragen werden können.
Macroservices
Neben den Abhängigkeiten zwischen den Diensten fällt auch eine Abhängigkeit zwischen den Entwicklerteams auf. Sind die Teams ungünstig geschnitten, wird dadurch automatisch auch der Kommunikationsaufwand zwischen den Teams erhöht. Dies spiegelt sich dann in regelmässigen Synchronisierungsmeetings wider, bei denen Anforderungen geklärt und Spezifikationen ausgetauscht werden müssen. In solchen Fällen machen Anpassungen an einem Dienst in einem Team auch Änderungen an Diensten eines anderen Teams notwendig. Diese kooperativen Änderungen wirken auf Dauer arbeitsbehindernd für die Teams und können sie dementsprechend stark ausbremsen.
Aufgrund der Granularität hat man somit einen Leistungsverlust sowohl zur Laufzeit als auch in der Entwicklungszeit zu beklagen. Je feiner die Granularität der Dienste ist, desto mehr verstärkt sich dieser Effekt. Aus diesem Grund wurde 2020 von Uber der Begriff der Macroservices geprägt. Dies kam zustande, da Uber mehr als 4000 Microservices im Einsatz hat und sich die zuvor genannten Reibungsverluste in Teilen als so stark erwiesen, dass entschieden wurde, Dienste zusammenzufassen, die ohnehin sehr stark miteinander verbunden sind. Bezogen auf die Beispiele aus diesem Artikel bedeutet dies also, statt jeweils einen eigenen Dienst für die Urlaubsbeantragung, die Stundenerfassung und die Krankmeldungen bereitzustellen, nur jeweils einen Dienst für die Personalverwaltung aufzubauen und diesen über mehrere Endpunkte ansprechbar zu machen. Diese Dienste sind nach wie vor fachlich ausgerichtet und noch voneinander separat deployfähig, sie haben aber einen grösseren Verantwortungsbereich.
Interessant zu wissen: Die Namensgebung Macroservices wurde von Uber deshalb genutzt, um den Unterschied zu sehr kleinteiligen Diensten hervorzuheben. Der Begriff selbst hat aber wenig positiven Nachhall erzeugt und wurde von weiten Teilen der Community eher so interpretiert, dass das eigentliche Konzept der Microservices von Uber nicht korrekt umgesetzt wurde. Inwiefern der Begriff in Zukunft überhaupt eine Bedeutung haben wird, bleibt abzuwarten.
Autor(in)
Hendrik
Lösch