Die Kosten für den Betrieb von Anwendungen in der Cloud können erheblich sein, selbst wenn die Anfangsinvestitionen gering erscheinen. Ähnlich wie beim Lift and Shift-Ansatz, bei dem die Anwendung in ihre neue Umgebung verschoben wird, stellt die Virtualisierung der Anwendung zu Beginn oft eine einfache Lösung dar. Sie ist ein guter erster Schritt, um klein anzufangen. Doch je mehr Anwendungen auf diese Weise betrieben werden und je mehr der Betrieb skaliert wird, desto deutlicher wird, dass dieser Ansatz zwar einen niedrigen Einstiegspreis hat, langfristig jedoch mit steigenden Aufwand verbunden ist. Daher empfiehlt es sich, die Anwendung zu containerisieren, um von den Vorteilen einer Cloud-nativen Architektur zu profitieren.

Containerisierung bedeutet, eine Anwendung so zu verpacken, dass sie in einem Container ausgeführt werden kann. Der Container wird dann in einer Container-Engine betrieben, die wiederum in einem Container-Orchestrator, wie beispielsweise Kubernetes, laufen kann. Der Vorteil dabei ist, dass die Anwendung selbst unverändert bleibt. Was sich ändert, ist die Art und Weise, wie sie installiert wird. Anstatt die Anwendung auf einem physischen oder virtuellen Server zu installieren, wird sie in einem Container betrieben. Dies ist besonders dann von Vorteil, wenn die Anwendungen in großen Mengen ausgeführt werden müssen und Skalierbarkeit sowie effiziente Verwaltung eine zentrale Rolle spielen.

Die Containerisierung stellt die nächste Evolution der Anwendungsbereitstellung dar, die über virtuelle Maschinen hinausgeht. Ein Container enthält die Anwendung sowie alle benötigten Betriebssystembibliotheken und kann somit in einer Container-Runtime ausgeführt werden. Im Vergleich zu einer virtuellen Maschine, die ein vollständiges Gastbetriebssystem benötigt, enthält ein Container nur die Bibliotheken, die die jeweilige Anwendung benötigt. Diese Reduktion macht Container erheblich kompakter und effizienter, was sie zu einer bevorzugten Lösung für Cloud-native Anwendungen macht.

Ein großer Vorteil der Containerisierung ist die hohe Dichte, mit der Container auf denselben Hardware-Ressourcen laufen können. Dank der Tatsache, dass Container den Kernel des zugrunde liegenden Betriebssystems gemeinsam nutzen, können mehr Container auf demselben Speicherplatz laufen als virtuelle Maschinen. Container starten zudem deutlich schneller als virtuelle Maschinen, da sie in einem gemeinsamen Prozessraum laufen und somit weniger Ressourcen benötigen, um hochzufahren. Dies ermöglicht eine schnellere Bereitstellung und ein effizienteres Management von Anwendungen.

Container sind auch weitaus portabler als virtuelle Maschinen. Dank des standardisierten Ansatzes für Container-Images und der Entwicklung von Container-Runtimes, die auf einer kleinen Anzahl von Open-Source-Projekten basieren, ist es möglich, Container über verschiedene Cloud-Umgebungen hinweg zu migrieren. So kann ein einmal erstellter Container problemlos zwischen verschiedenen Plattformen und On-Premise-Infrastrukturen verschoben werden. Diese Flexibilität hat entscheidende Auswirkungen auf die Effizienz und Kosten von Cloud-Diensten.

Containerisieren Sie Ihre Anwendung bedeutet jedoch nicht, dass jede bestehende Anwendung einfach in einen Container übertragen werden kann. Einige Anwendungen stellen Anforderungen an das Betriebssystem oder an Middleware, die nicht sofort mit einer Containerplattform kompatibel sind. Vor allem Anwendungen, die auf älteren Versionen von Betriebssystemen oder spezialisierten Middleware-Plattformen laufen, benötigen eine gründliche Überprüfung, um sicherzustellen, dass sie in einem Container betrieben werden können. In diesen Fällen könnte eine Virtualisierung der Anwendung die bessere Option sein.

Für die Containerisierung einer Anwendung sind einige grundlegende Faktoren zu berücksichtigen: Zunächst muss die Anwendung in einem Container-basierten Betriebssystem wie Linux oder Windows lauffähig sein. Hierbei handelt es sich um die beiden am häufigsten unterstützten Betriebssysteme, die für Containerisierung zur Verfügung stehen. Zudem muss sichergestellt werden, dass alle erforderlichen Middleware-Anwendungen, wie z. B. Datenbankserver oder Anwendungsserver, ebenfalls in einem Container laufen können. Wenn dies nicht der Fall ist, könnte es notwendig sein, die Anwendung auf eine kompatible Version zu migrieren oder spezielle Anpassungen vorzunehmen.

Ein weiterer Punkt ist die Erstellung des Container-Images. Wenn die Anwendung in einer gängigen Programmiersprache entwickelt wurde und auf einem unterstützten Betriebssystem läuft, ist die Containerisierung in der Regel ein einfacher Prozess. Entwickler können auf Basis eines Standard-Images des Anwendungsservers oder einer Laufzeitumgebung ein Container-Image erstellen. Wenn jedoch keine Standard-Images verfügbar sind, müssen Entwickler eigene Skripte schreiben, um das Image zu erstellen und die Middleware korrekt zu installieren. Dies kann besonders dann eine Herausforderung darstellen, wenn die Installation von Drittanbieter-Middleware schwierig automatisiert werden kann.

Die Vorteile der Containerisierung sind jedoch unbestreitbar: Sie ermöglicht eine höhere Portabilität, da die gleichen Container-Images in verschiedenen Cloud-Umgebungen und lokalen Infrastrukturen betrieben werden können. Außerdem spart die hohe Dichte von Containern auf derselben Hardware Ressourcen, was zu einer besseren Skalierbarkeit führt. Anwendungen werden schneller bereitgestellt und lassen sich zuverlässiger verwalten. Dies unterstützt die Idee der Microservices-Architektur, bei der monolithische Anwendungen in kleinere, besser handhabbare Komponenten aufgeteilt werden.

Dennoch ist der Übergang zur Containerisierung nicht ohne Herausforderungen. Viele bestehende Entwicklungs- und Bereitstellungsautomatisierungen wurden ursprünglich für virtuelle Serverumgebungen entwickelt und müssen angepasst werden, um mit Containern effektiv arbeiten zu können. Diese Umstellung erfordert sowohl Zeit als auch Ressourcen, was für viele Unternehmen eine Hürde darstellt.

Es ist jedoch wichtig zu verstehen, dass Container nicht nur eine technische Lösung bieten, sondern auch eine neue Denkweise im Umgang mit Softwarebereitstellung und -management erfordern. Die Vorteile in Bezug auf Flexibilität, Skalierbarkeit und Effizienz sind gewaltig, aber die Containerisierung erfordert eine gut durchdachte Planung und eine gründliche Vorbereitung der bestehenden IT-Infrastruktur.

Wie man Monolithen stranguliert: Strategien und Ansätze zur Migration zu Microservices

Im Rahmen der Modernisierung und Refaktorisierung von Systemen, insbesondere beim Übergang von monolithischen Architekturen zu Microservices, kommt häufig ein Konzept zur Anwendung, das als "Strangulieren" bezeichnet wird. Dieses Vorgehen hilft dabei, eine bestehende monolithische Struktur schrittweise durch eine Microservices-Architektur zu ersetzen, ohne das gesamte System auf einmal umbauen zu müssen. Ein gutes Beispiel für eine solche Umstellung ist das Vorgehen einer großen US-amerikanischen Finanzinstitution, die ein neues System auf Basis von CQRS (Command Query Responsibility Segregation) entwickelte. In diesem Fall wurden die Modelle für das Schreiben und Lesen von Daten voneinander getrennt, was eine moderne und flexible Architektur darstellt. Die Herausforderung lag darin, das alte System zu migrieren, ohne dass es zu Unterbrechungen in der Geschäftslogik oder im Betrieb kam.

Ein effektiver Bestandteil des Übergangs war die Verwendung von Playback Testing, um sicherzustellen, dass die neuen Microservices die gleichen Ergebnisse lieferten wie das alte System, bevor letzteres vollständig abgeschaltet wurde. Playback Testing wurde eingesetzt, um die Berichterstattung im alten und neuen System gleichzeitig zu überprüfen. Erst als die Berichte aus beiden Systemen identisch waren, wurde das alte System endgültig stillgelegt. Diese Methode ermöglichte es, eine vollständige Überprüfung der neuen Architektur durchzuführen, ohne dass die Geschäftsprozesse gestört wurden.

Die Entscheidung, ob ein monolithisches System vollständig umgebaut oder nur refaktoriert werden sollte, stellt sich in vielen Unternehmen. Dabei ist es wichtig, dass die Entscheidung auf den spezifischen Anforderungen und der Komplexität des bestehenden Systems basiert. In einigen Fällen kann eine vollständige Neugestaltung sinnvoll sein, in anderen wiederum reicht eine schrittweise Umstellung oder eine gezielte Refaktorisierung des Monolithen aus.

Das Strangulieren eines Monolithen ist ein iterativer Prozess, der mit der Identifikation und Extraktion von Funktionalitäten aus dem alten System beginnt. Der erste Schritt dabei ist häufig, neue Funktionen direkt als Microservices zu implementieren, um zu verhindern, dass das Monolithen-System weiter wächst. Dadurch wird der Druck erhöht, bestehende Funktionen durch Microservices zu ersetzen, was den Übergang erleichtert. Dieser Prozess kann von der Entwicklung kleinerer Microservices für spezifische Aufgaben bis hin zur Umstrukturierung größerer Teile des Systems reichen, die dann nach und nach durch Microservices ersetzt werden.

Mögliche Szenarien für das Strangulieren eines Monolithen beinhalten unter anderem die Implementierung neuer Funktionen als Microservices, das Extrahieren von problematischen Komponenten sowie das Ersetzen veralteter Protokolle und Technologien. Das Extrahieren von funktionalen Komponenten, die schwierig zu ändern sind oder die die Skalierbarkeit und Wartbarkeit des Systems behindern, ist ein weiteres typisches Ziel im Rahmen des Umstiegs. Dabei ist es von Vorteil, gezielt nach sogenannten „Hairline Cracks“ im Monolithen zu suchen. Dies sind Stellen im Code, die aufgrund ihrer lose gekoppelten Struktur gut für die Extraktion geeignet sind. Diese Komponenten können entweder direkt als Microservices implementiert oder, falls notwendig, zunächst als größere Macro Services ausgelagert und später in kleinere Microservices unterteilt werden.

Ein häufiges Problem bei der Extraktion von Monolithen besteht darin, dass viele Teile des Systems eng miteinander verbunden sind und eine direkte Extraktion schwierig oder sogar unmöglich ist. In solchen Fällen ist es ratsam, den Monolithen zuerst in größere Teile zu zerlegen, bevor man mit der Schaffung von Microservices fortfährt. Während dieses Prozesses ist die Verwendung von Monolith to Microservice Proxies von entscheidender Bedeutung, um eine nahtlose Integration zwischen den neuen Microservices und dem noch existierenden Monolithen zu gewährleisten.

Wichtig zu beachten ist, dass die Umstellung auf Microservices nicht nur eine technische Herausforderung darstellt, sondern auch tiefgreifende organisatorische Veränderungen mit sich bringt. Ein funktionierendes Team benötigt nicht nur technisches Wissen über Microservices und die damit verbundenen Technologien, sondern auch ein Verständnis für die neue Art der Zusammenarbeit, die notwendig ist, um diese Architektur effizient zu nutzen. Ein gutes Vorgehen ist es, mit kleinen Microservices zu beginnen, die klare Schnittstellen und gut definierte Verantwortlichkeiten haben, um so eine schrittweise Einführung zu ermöglichen und den Übergang zu erleichtern.

Die Einführung von Microservices erfordert ebenfalls eine kontinuierliche Validierung des Systems. Dies kann durch verschiedene Testmethoden wie Playback Testing geschehen, bei dem überprüft wird, ob die extrahierten oder neu implementierten Microservices die gleichen Ergebnisse wie das alte System liefern. Diese Form der Teststrategie sorgt dafür, dass keine Funktionalität verloren geht und dass der Übergang sowohl aus funktionaler als auch aus betrieblicher Sicht reibungslos verläuft.

Das Strangulieren eines Monolithen ist letztlich ein flexibles und anpassungsfähiges Konzept, das es Teams ermöglicht, die Vorteile von Microservices zu nutzen, ohne die gesamte Architektur auf einmal umzustellen. Diese Strategie erfordert Geduld, sorgfältige Planung und kontinuierliches Testen, um sicherzustellen, dass die Umstellung auf Microservices tatsächlich zu den gewünschten Ergebnissen führt und das System effizienter, skalierbarer und wartungsfreundlicher wird.

Wie ein Dispatcher Microservices für unterschiedliche Clients zugänglich macht

In modernen Cloud-nativen Anwendungen, insbesondere in Microservices-Architekturen, ist die Interaktion zwischen verschiedenen Clients und einer dynamischen Sammlung von Microservices ein zentrales Thema. Dabei wird oft ein grundlegendes Problem sichtbar: Die Microservices bieten eine Vielzahl unterschiedlicher APIs, während die Clients, seien es Webbrowser, mobile Apps oder Unternehmenskunden, eine stabilisierte, einfach zu bedienende Schnittstelle benötigen. Hier kommt der Dispatcher ins Spiel.

Der Dispatcher agiert als Bindeglied zwischen den Clients und der Microservices-Architektur und stellt eine einheitliche API bereit, die den Clients den Zugang zu den verschiedenen Microservices ermöglicht, ohne dass diese mit den komplexen und sich ständig verändernden Details der Architektur konfrontiert werden müssen. Während Microservices-Architekturen in der Regel aus zahlreichen einzelnen Services bestehen, die spezifische Geschäftslogiken implementieren, benötigt der Client einen einfachen Zugriff auf diese Funktionalitäten, oft ohne sich um die interne Struktur und Organisation der Microservices kümmern zu müssen.

Ein Dispatcher, oft auch als Backend for Frontend (BFF) bezeichnet, stellt genau diese Brücke dar. Er bietet eine einzige, stabilisierte API, über die der Client auf die gesamte Businesslogik zugreifen kann, die in den verschiedenen Microservices verteilt ist. Dies ist besonders wichtig, weil die Microservices innerhalb der Architektur ständig verändert, refaktoriert und weiterentwickelt werden, während der Dispatcher eine konstante API beibehält, die die Clients weiterhin nutzen können, ohne ihre eigene Implementierung anzupassen.

Nehmen wir als Beispiel eine E-Commerce-Anwendung, die aus mehreren Microservices besteht: Katalog, Kunden, Bestellungen sowie zwei Adapter-Microservices, die bestehende Systeme wie das Bestands- und Zahlungssystem integrieren. Die Adapter-Microservices stellen APIs bereit, die es der Microservices-Architektur ermöglichen, mit älteren, möglicherweise unübersichtlichen Systemen zu interagieren, ohne dass die Clients mit deren Komplexität umgehen müssen. Diese Adapter-Microservices bieten eine saubere, abstrahierte API und verbessern so die Nutzererfahrung und Sicherheit. Sie verschleiern die oft unhandlichen Interfaces der bestehenden Systeme und bieten gleichzeitig eine Möglichkeit, die alten Systeme durch neue, moderne Microservices zu ersetzen.

Ein Dispatcher kann jedoch mehr als nur eine einfache API bereitstellen. Er fungiert auch als eine Art Übersetzer, der verschiedene Versionen von Microservices und deren APIs in eine für den Client verständliche und stabile Schnittstelle umwandelt. Wenn die Microservices API-Änderungen durchlaufen, bleibt der Dispatcher unverändert, was den Clients eine kontinuierliche Nutzung der Dienste ermöglicht, ohne dass sie mit den Anpassungen der Microservices-Architektur konfrontiert werden. Dies ermöglicht eine hohe Flexibilität und Skalierbarkeit für die Backend-Architektur, ohne dass der Frontend-Client regelmäßig angepasst werden muss.

Darüber hinaus können Dispatchers unterschiedliche Varianten für verschiedene Clients implementieren. Ein Web-Client benötigt möglicherweise eine andere API als ein mobiler Client oder ein Partner-API. Der Dispatcher stellt sicher, dass jeder Client genau die Informationen und Funktionalitäten erhält, die er benötigt, ohne die gesamte Architektur neu gestalten zu müssen. Diese Flexibilität ist besonders wichtig, wenn verschiedene Anwendungen oder Plattformen unterschiedliche Anforderungen an die Präsentation von Daten und Geschäftslogik haben.

Ein weiterer Aspekt, der oft übersehen wird, ist, dass Dispatcher nicht nur als Front-End für verschiedene Client-Typen dienen, sondern auch eine wichtige Rolle beim Schutz der Microservices-Architektur vor Änderungen spielen, die durch externe Clients oder neue Anforderungen entstehen. Wenn ein Client neue Funktionalitäten oder Änderungen an bestehenden APIs benötigt, sorgt der Dispatcher dafür, dass diese Änderungen nahtlos integriert werden, ohne dass bestehende Microservices oder die gesamte Architektur umgestaltet werden müssen. Diese Entkopplung zwischen den Clients und den Microservices ist entscheidend für die langfristige Wartbarkeit und Flexibilität der Anwendung.

Ein Dispatcher schützt also nicht nur die Clients vor Änderungen der Microservices-Architektur, sondern stellt auch sicher, dass die Microservices weiterhin effizient miteinander kommunizieren können, ohne dass die Architektur ständig angepasst werden muss. Durch diese Entkopplung wird es möglich, Microservices kontinuierlich zu verbessern und neue hinzu zu fügen, während bestehende Clients weiterhin eine stabile und benutzerfreundliche API nutzen können.

Der Einsatz von Dispatchern in Microservices-Architekturen ist somit ein Schlüssel zur Schaffung einer robusten, skalierbaren und flexiblen Anwendung, die sowohl die Herausforderungen der dynamischen Cloud-Umgebungen bewältigt als auch den Bedürfnissen der Endanwender gerecht wird.

Ein weiterer wichtiger Punkt, den man nicht übersehen sollte, ist, dass Dispatchers nicht nur bei der Konsolidierung von APIs helfen, sondern auch bei der Verwaltung der Sicherheit. Da ein Dispatcher als zentrale Zugangsstelle fungiert, kann er Sicherheitsmaßnahmen wie Authentifizierung, Autorisierung und Verschlüsselung implementieren, um sicherzustellen, dass die Kommunikation zwischen den Clients und den Microservices geschützt ist.