“Beziehungen bleiben bestehen aber Bedürfnisse ändern sich“. So oder so ähnlich heißt es in einer schlüpfrigen Fernsehwerbung zu später Stunde. Bei Microservices finden sich aber tatsächlich immer wieder Situationen, bei denen zwei Microservices besser harmonieren, wenn ein dritter involviert ist.
Eine Microservice-basierte Anwendung besteht aus einer mehr oder minder großen Gruppen von Microservices, die von anderen Anwendungen genutzt werden könnten. Diese Anwendungen könnten theoretisch auf all diese Microservices zugreifen. Es gibt jedoch gute Gründe, nicht alle Microservices öffentlich zugänglich zu machen.
Je mehr Microservices öffentlich zugänglich sind, desto mehr werden die Möglichkeiten der Weiterentwicklung eingeschränkt. Eine öffentliche API kann nicht so einfach und ohne Absprachen geändert werden, wie eine interne Implementierung. Auch die Gegenseite hat kein großes Interesse viele unterschiedliche Microservices anzusprechen. Gibt es Änderungen an deren APIs, dann ist immer an irgendeiner Stelle der eigenen Anwendung etwas anzupassen. Es ist also ökonomischer für die Entwicklung den öffentlichen Zugriff zu minimieren.
Zudem sind Mikroservices Spezialisten für ein kleines Fachgebiet, die aufrufenden Anwendungen wollen jedoch komplexe Aufgaben beauftragen. Dazu müssen sie dann mit mehreren Microservices kommunizieren und viel Zeit mit teurer, externer Kommunikation verbringen. Hier wäre ein Mittelsmann vor Ort erwünscht, der die externen Anforderungen in die entsprechenden Microservice Aufrufe umsetzt.
Das bekannteste Microservice Pattern in diesem Zusammenhang ist das Gateway. Es entspricht dem Design Pattern Facade bzw. Proxy in der Objekt-Orientierten Entwicklung. Das Gateway ist ein wahrer Tausendsasse am Eingangstor zur Microservice Anwendung. Grundsätzlich arbeitet es als Facade für die Microservices der Anwendungen. In der folgenden Abbildung sieht man, dass einige Dienste von Mikroservice 2 und Mikroservice 4 von außen erreichbar sind, alle Dienste von Mikroservice 1 und Mikroservice 3 aber nur intern zur Verfügung stehen.
Als Proxy zwischen der Außenwelt und den Microservices kann das Gateway aber noch viele andere Aufgaben übernehmen. Beispielsweise kann geprüft werden, ob eine externe Anwendung aus einem zulässigen IP Nummernkreis heraus arbeitet, ob diese Anwendung für die angeforderten Dienste authentisiert ist oder ob das Kontingent an Dienstleistungen schon erschöpft wurde.
Auch kann ein Gateway als Cache funktionieren oder eingehende Anfragen und ausgehende Antworten modifizieren. Bei verteilten Anwendungen ist die Fehlersuche mit Schwierigkeiten verbunden, weil die Anfrage über viele verschiedene Microservices hinweg bearbeitet wird. Dann ist eine einfache Zuordnung über eine eindeutige Bezeichnung hilfreich. Hier kann das Gateway helfen, indem es eingehende Anfragen mit einer UUID versieht und dieses Interna bei ausgehenden Antworten entfernt.
Eine besondere Variante des Gateway ist das Backend for Frontend Pattern. Häufig benötigen unterschiedliche Client Anwendungen unterschiedliche Datenformate oder müssen mit einem reduzierten Set zurecht kommen. Daher empfiehlt es sich zwischen einem IoT Gateway, das z.B. Kühlschränke und Rasenmäher mit Daten versorgt und einem Mobile Gateway für Humanoide zu unterscheiden.
Die nächsten Microservice Patterns ähneln sich in ihrer Darstellung, weil sie immer ein Gateway enthalten, dass mehrere Microservices anspricht. Das erste Pattern ist die Aggregation und behandelt das zu Beginn angesprochene Komplexitätsproblem für den Nutzer der Mikroservice Anwendung. Im folgenden Diagramm ist ein Client gezeigt, der eine Dienstleistung von der Anwendung erfüllt bekommen möchte.
Leider muss er dafür die drei Microservices nacheinander aufrufen und dabei vielleicht sogar die Antwort eines Microservice verwenden um einen anderen zu parametrisieren. Zusätzlich muss daran gedacht werden, die Fehlerbehandlung von drei Microservices zu implementieren. Sehr aufwendig und fehleranfällig, wenn der Entwickler des Clients nicht gut vertraut mit den Microservices ist.
Die Lösung für dieses Problem ist ein Aggregator, der zwischen Client und Microservices vermittelt. Der Entwickler des Clients hat eine sehr viel einfachere Schnittstelle zu implementieren und die Datenübertragung zwischen Client und Anwendung wird auch stark reduziert. Zukünftige Änderungen an der Anwendungen können vorgenommen werden ohne den Client anpassen zu müssen. Im letzten Beitrag wurde die Choreografie über eine Event-Driven Architektur vorgestellt. Selbstverständlich kann in diesem Beispiel der Aggregator mit Events arbeiten, er kann aber auch den sequentiellen Aufruf der Mikroservices fest verdrahten oder sogar eine Workflow-Engine integrieren. Wer statt Choreografie lieber diese Orchestrierung verwenden möchte, sollte aber immer den Flaschenhals des Dirigenten beachten. Dieser bremst die Architektur und das agile Arbeiten an der Anwendung immer aus.
Die letzten drei Microservice Pattern beschäftigen sich mit der Integration von Legacy Anwendungen in die Microservice Anwendung.
Das Strangler Pattern beschreibt eine Lösung um Legacy Software langsam verschwinden zu lassen. Es entspricht dem Branch By Abstraction Pattern bei Objekten.
Der Strangler ist eine Weiche zwischen einer alten Legacy Implementierung und neuen Implementierungen durch Microservices. Ein Aufruf wird anfangs vom Strangler direkt an die Legacy Implementierung durchgereicht. Sobald eine neue Implementierung bereitsteht wird auf diese umgeschaltet. Dies kann hart oder weich erfolgen. Bei einer weichen Umschaltung können beide Implementierung parallel weiterarbeiten, werden aber miteinander abgeglichen. So kann zu Beginn die Neuimplementierung im Regelbetrieb geprüft und verbessert werden. Nachdem die Legacy Funktionalität deaktiviert wurde, kann an dieser Stelle der Strangler als Mittelsmann entfernt werden.
Sehr ähnlich schaut ein Gateway aus, dass als Proxy vor mehreren ähnlichen Microservices fungiert. Je nachdem welcher Kunde anfragt, wird entweder der Default Mikroservice verwendet oder ein kundenspezifischer.
Dieses Pattern taucht hier bei den Legacy Pattern auf, weil kundenspezifische Mikroservices ihren Ursprung immer in schlecht migrierter Legacy Software haben.
Das letzte Pattern im Bereich der Legacy Pattern und auch das letzte Pattern in diesem Beitrag ist das Anti-Corruption Layer Pattern. Wenn eine neue Architektur erdacht und alle Schnittstellen und Daten sauber modelliert wurden, dann ist Vorsicht geraten, wenn Legacy Systeme angeschlossen werden sollen. Häufig verwässern dann die Datenmodelle oder die Schnittstellen werden korrigiert, damit die alten Daten und Aufrufe, irgendwie ins neue Konzept passen. Nach einiger Zeit sieht dann die neue Software leider wie die alte aus.
Dagegen hilft der Anti-Corruption Layer, der ist nicht anderes als ein Proxy zwischen Microservices und Legacy Software ist. Er transformiert alte Daten in das neue Format und bildet neue Schnittstellen auf alte API-Aufrufe ab. Er ist die Schmuddelbude der neuen Architektur, die solange besteht, wie die Legacy Software nicht abgelöst werden kann.
Dieser Beitrag zeigt, dass die Microservices Architektur viele Pattern bereitstellt, um alltägliche Probleme in der Software Entwicklung zu beheben. Wichtig ist jedoch zu erkennen, dass die Mikroservices Architektur keine wirklich neuen Design Pattern liefert, sondern die Entwicklung und den Betrieb von Anwendungen durch die starke Entkopplung der Services vorantreibt.