Redaktion – Die siebenteilige Artikelserie ist nun komplett:
Sie können den kompletten Artikelsatz sowie Informationen zur Implementierung von Microservices mit NGINX Plus auch als E-Book herunterladen – Microservices: Vom Entwurf bis zur Bereitstellung . Und sehen Sie sich unsere Serie zur Microservices-Referenzarchitektur und die Seite mit Microservices-Lösungen an.
Dies ist der siebte und letzte Artikel meiner Reihe zum Erstellen von Anwendungen mit Microservices. Der erste Artikel stellt das Microservices-Architekturmuster vor und diskutiert die Vorteile und Nachteile der Verwendung von Microservices. Die folgenden Artikel behandeln verschiedene Aspekte der Microservices-Architektur: Verwenden eines API-Gateways<.htmla>, Interprozesskommunikation , Serviceerkennung , ereignisgesteuertes Datenmanagement und Bereitstellen von Microservices . In diesem Artikel betrachten wir Strategien zur Migration einer monolithischen Anwendung zu Microservices.
Ich hoffe, dass Ihnen diese Artikelserie ein gutes Verständnis der Microservices-Architektur, ihrer Vor- und Nachteile und ihrer Einsatzmöglichkeiten vermittelt hat. Möglicherweise passt die Microservices-Architektur gut zu Ihrer Organisation.
Es besteht jedoch eine ziemlich große Wahrscheinlichkeit, dass Sie an einer großen, komplexen monolithischen Anwendung arbeiten. Die tägliche Entwicklung und Bereitstellung Ihrer Anwendung ist langsam und mühsam. Microservices scheinen ein fernes Nirvana zu sein. Glücklicherweise gibt es Strategien, mit denen Sie der monolithischen Hölle entkommen können. In diesem Artikel beschreibe ich, wie man eine monolithische Anwendung schrittweise in eine Reihe von Microservices umwandelt.
Der Prozess der Umwandlung einer monolithischen Anwendung in Microservices ist eine Form der Anwendungsmodernisierung . Das ist etwas, was Entwickler seit Jahrzehnten tun. Daher gibt es einige Ideen, die wir beim Refactoring einer Anwendung in Microservices wiederverwenden können.
Eine Strategie, die Sie nicht anwenden sollten, ist die „Big Bang“-Umschreibung. In diesem Fall konzentrieren Sie alle Ihre Entwicklungsanstrengungen darauf, von Grund auf eine neue, auf Microservices basierende Anwendung zu erstellen. Obwohl es verlockend klingt, ist es äußerst riskant und wird wahrscheinlich scheitern. Martin Fowler soll gesagt haben : „Das einzige, was eine Neufassung des Urknalls garantiert, ist ein Urknall!“
Anstatt eine Big Bang-Neufassung vorzunehmen, sollten Sie Ihre monolithische Anwendung schrittweise umgestalten. Sie erstellen schrittweise eine neue Anwendung, die aus Microservices besteht, und führen sie zusammen mit Ihrer monolithischen Anwendung aus. Mit der Zeit verringert sich der Umfang der von der monolithischen Anwendung implementierten Funktionalität, bis sie entweder ganz verschwindet oder nur noch zu einem weiteren Mikrodienst wird. Diese Strategie ist vergleichbar mit der Wartung Ihres Autos während der Fahrt mit 110 km/h auf der Autobahn – anspruchsvoll, aber weit weniger riskant als der Versuch einer Big-Bang-Umschreibung.
Martin Fowler bezeichnet diese Strategie zur Anwendungsmodernisierung als Strangler Application . Der Name kommt von der Würgefeige, die in Regenwäldern wächst. Eine Würgeranke wächst um einen Baum herum, um an das Sonnenlicht über dem Blätterdach des Waldes zu gelangen. Manchmal stirbt der Baum ab und hinterlässt eine baumförmige Rebe. Die Anwendungsmodernisierung folgt demselben Muster. Wir werden eine neue Anwendung bestehend aus Microservices rund um die Legacy-Anwendung erstellen, die irgendwann nicht mehr existieren wird.
Sehen wir uns hierzu verschiedene Strategien an.
Das Gesetz der Löcher besagt, dass Sie mit dem Graben aufhören sollten, wenn Sie sich in einem Loch befinden. Dies ist ein guter Rat, den Sie befolgen sollten, wenn Ihre monolithische Anwendung unhandlich geworden ist. Mit anderen Worten: Sie sollten aufhören, den Monolithen zu vergrößern. Dies bedeutet, dass Sie beim Implementieren neuer Funktionen dem Monolithen keinen weiteren Code hinzufügen sollten. Die große Idee dieser Strategie besteht vielmehr darin, den neuen Code in einen eigenständigen Mikroservice zu integrieren. Das folgende Diagramm zeigt die Systemarchitektur nach Anwendung dieses Ansatzes.
Neben dem neuen Dienst und dem alten Monolithen gibt es zwei weitere Komponenten. Der erste ist ein Anforderungsrouter, der eingehende (HTTP-)Anfragen verarbeitet. Es ähnelt dem in einem früheren Artikel<.htmla> beschriebenen API-Gateway. Der Router sendet Anfragen entsprechend der neuen Funktionalität an den neuen Dienst. Es leitet Legacy-Anfragen an den Monolithen weiter.
Die andere Komponente ist der Klebecode, der den Dienst in den Monolithen integriert. Ein Dienst existiert selten isoliert und muss häufig auf Daten zugreifen, die dem Monolithen gehören. Für die Datenintegration ist der Glue-Code zuständig, der sich entweder im Monolithen, im Dienst oder in beiden befindet. Der Dienst verwendet den Klebecode zum Lesen und Schreiben von Daten, die dem Monolithen gehören.
Es gibt drei Strategien, die ein Dienst verwenden kann, um auf die Daten des Monolithen zuzugreifen:
Der Klebecode wird manchmal als Antikorruptionsschicht bezeichnet. Der Grund hierfür ist, dass der Verbindungscode verhindert, dass der Dienst, der über sein eigenes, makelloses Domänenmodell verfügt, durch Konzepte aus dem Domänenmodell des alten Monolithen verunreinigt wird. Der Klebecode dient zur Übersetzung zwischen den beiden unterschiedlichen Modellen. Der Begriff „Anti-Corruption Layer“ tauchte erstmals in dem unverzichtbaren Buch „Domain Driven Design“ von Eric Evans auf und wurde dann in einem Whitepaper verfeinert. Die Entwicklung einer Antikorruptionsebene kann ein nicht triviales Unterfangen sein. Aber die Schaffung eines solchen Systems ist unabdingbar, wenn Sie aus der monolithischen Hölle herauswachsen möchten.
Die Implementierung neuer Funktionen als einfacher Dienst bietet mehrere Vorteile. Es verhindert, dass der Monolith noch unkontrollierbarer wird. Der Dienst kann unabhängig vom Monolithen entwickelt, bereitgestellt und skaliert werden. Sie profitieren von den Vorteilen der Microservices-Architektur bei jedem neuen Dienst, den Sie erstellen.
Dieser Ansatz trägt jedoch nicht dazu bei, die Probleme mit dem Monolithen zu lösen. Um diese Probleme zu beheben, müssen Sie den Monolithen aufbrechen. Sehen wir uns Strategien dazu an.
Eine Strategie zur Verkleinerung der monolithischen Anwendung besteht darin, die Präsentationsschicht von der Geschäftslogik- und der Datenzugriffsschicht zu trennen. Eine typische Unternehmensanwendung besteht aus mindestens drei verschiedenen Komponententypen:
Normalerweise besteht eine klare Trennung zwischen der Präsentationslogik auf der einen Seite und der Geschäfts- und Datenzugriffslogik auf der anderen Seite. Die Business-Ebene verfügt über eine grobkörnige API, die aus einer oder mehreren Fassaden besteht, die Komponenten der Geschäftslogik kapseln. Diese API ist eine natürliche Naht, entlang der Sie den Monolithen in zwei kleinere Anwendungen aufteilen können. Eine Anwendung enthält die Präsentationsschicht. Die andere Anwendung enthält die Geschäfts- und Datenzugriffslogik. Nach der Aufteilung tätigt die Präsentationslogikanwendung Remoteaufrufe an die Geschäftslogikanwendung. Das folgende Diagramm zeigt die Architektur vor und nach dem Refactoring.
Das Aufteilen eines Monolithen auf diese Weise hat zwei Hauptvorteile. Es ermöglicht Ihnen, die beiden Anwendungen unabhängig voneinander zu entwickeln, bereitzustellen und zu skalieren. Insbesondere können die Entwickler der Präsentationsebene schnell an der Benutzeroberfläche iterieren und beispielsweise problemlos A/B-Tests durchführen. Ein weiterer Vorteil dieses Ansatzes besteht darin, dass er eine Remote-API bereitstellt, die von den von Ihnen entwickelten Microservices aufgerufen werden kann.
Diese Strategie ist jedoch nur eine Teillösung. Es ist sehr wahrscheinlich, dass eine oder beide Anwendungen ein unhandlicher Monolith sein werden. Sie müssen die dritte Strategie verwenden, um den oder die verbleibenden Monolithen zu eliminieren.
Die dritte Refactoring-Strategie besteht darin, vorhandene Module innerhalb des Monolithen in eigenständige Microservices umzuwandeln. Jedes Mal, wenn Sie ein Modul extrahieren und es in einen Dienst umwandeln, schrumpft der Monolith. Sobald Sie genügend Module konvertiert haben, stellt der Monolith kein Problem mehr dar. Entweder verschwindet es vollständig oder es wird so klein, dass es nur noch ein weiterer Dienst ist.
Eine große, komplexe monolithische Anwendung besteht aus Dutzenden oder Hunderten von Modulen, die alle für die Extraktion in Frage kommen. Es ist oft eine Herausforderung herauszufinden, welche Module zuerst konvertiert werden sollen. Ein guter Ansatz besteht darin, mit wenigen Modulen zu beginnen, die leicht zu extrahieren sind. Dadurch sammeln Sie Erfahrungen mit Microservices im Allgemeinen und dem Extraktionsprozess im Besonderen. Anschließend sollten Sie die Module extrahieren, die Ihnen den größten Nutzen bringen.
Die Konvertierung eines Moduls in einen Dienst ist normalerweise zeitaufwändig. Sie möchten Module nach dem Nutzen ordnen, den Sie daraus ziehen. Normalerweise ist es von Vorteil, Module zu extrahieren, die sich häufig ändern. Sobald Sie ein Modul in einen Dienst umgewandelt haben, können Sie es unabhängig vom Monolithen entwickeln und bereitstellen, was die Entwicklung beschleunigt.
Es ist auch vorteilhaft, Module zu extrahieren, deren Ressourcenanforderungen sich deutlich von denen des Rests des Monolithen unterscheiden. Dies ist beispielsweise nützlich, um ein Modul mit einer In-Memory-Datenbank in einen Dienst umzuwandeln, der dann auf Hosts mit großen Speichermengen bereitgestellt werden kann. Ebenso kann es lohnend sein, Module zu extrahieren, die rechenintensive Algorithmen implementieren, da der Dienst dann auf Hosts mit vielen CPUs bereitgestellt werden kann. Indem Sie Module mit besonderem Ressourcenbedarf in Dienste umwandeln, können Sie die Skalierung Ihrer Anwendung wesentlich vereinfachen.
Wenn Sie herausfinden, welche Module extrahiert werden sollen, ist es hilfreich, nach vorhandenen grobkörnigen Grenzen (auch als Nähte bezeichnet) zu suchen. Sie machen es einfacher und kostengünstiger, Module in Dienste umzuwandeln. Ein Beispiel für eine solche Grenze ist ein Modul, das nur über asynchrone Nachrichten mit dem Rest der Anwendung kommuniziert. Es kann relativ günstig und einfach sein, dieses Modul in einen Mikrodienst umzuwandeln.
Der erste Schritt beim Extrahieren eines Moduls besteht darin, eine grobkörnige Schnittstelle zwischen dem Modul und dem Monolithen zu definieren. Es handelt sich höchstwahrscheinlich um eine bidirektionale API, da der Monolith Daten benötigt, die dem Dienst gehören, und umgekehrt. Aufgrund der komplexen Abhängigkeiten und feinkörnigen Interaktionsmuster zwischen dem Modul und dem Rest der Anwendung ist die Implementierung einer solchen API häufig eine Herausforderung. Die Umgestaltung der mit dem Domänenmodellmuster implementierten Geschäftslogik ist aufgrund zahlreicher Assoziationen zwischen Domänenmodellklassen besonders schwierig. Um diese Abhängigkeiten aufzuheben, müssen Sie häufig erhebliche Codeänderungen vornehmen. Das folgende Diagramm zeigt das Refactoring.
Sobald Sie die grobkörnige Schnittstelle implementiert haben, machen Sie aus dem Modul einen eigenständigen Dienst. Dazu müssen Sie Code schreiben, der es dem Monolithen und dem Dienst ermöglicht, über eine API zu kommunizieren, die einen Mechanismus zur Interprozesskommunikation (IPC) verwendet. Das folgende Diagramm zeigt die Architektur vor, während und nach dem Refactoring.
In diesem Beispiel ist Modul Z das zu extrahierende Kandidatenmodul. Seine Komponenten werden von Modul X verwendet und es verwendet Modul Y. Der erste Refactoring-Schritt besteht darin, ein Paar grobkörniger APIs zu definieren. Die erste Schnittstelle ist eine eingehende Schnittstelle, die von Modul X verwendet wird, um Modul Z aufzurufen. Die zweite ist eine ausgehende Schnittstelle, die von Modul Z verwendet wird, um Modul Y aufzurufen.
Der zweite Refactoring-Schritt macht aus dem Modul einen eigenständigen Dienst. Die eingehenden und ausgehenden Schnittstellen werden durch Code implementiert, der einen IPC-Mechanismus verwendet. Sie müssen den Dienst höchstwahrscheinlich erstellen, indem Sie Modul Z mit einem Microservice Chassis-Framework kombinieren, das übergreifende Belange wie die Diensterkennung behandelt.
Sobald Sie ein Modul extrahiert haben, verfügen Sie über einen weiteren Dienst, der unabhängig vom Monolithen und allen anderen Diensten entwickelt, bereitgestellt und skaliert werden kann. Sie können den Dienst sogar von Grund auf neu schreiben. In diesem Fall wird der API-Code, der den Dienst in den Monolithen integriert, zu einer Antikorruptionsschicht, die zwischen den beiden Domänenmodellen übersetzt. Jedes Mal, wenn Sie einen Dienst extrahieren, machen Sie einen weiteren Schritt in Richtung Microservices. Mit der Zeit wird der Monolith kleiner und Sie werden über eine zunehmende Anzahl an Mikroservices verfügen.
Der Prozess der Migration einer vorhandenen Anwendung in Microservices ist eine Form der Anwendungsmodernisierung. Sie sollten nicht zu Microservices wechseln, indem Sie Ihre Anwendung von Grund auf neu schreiben. Stattdessen sollten Sie Ihre Anwendung schrittweise in eine Reihe von Microservices umgestalten. Sie können drei Strategien verwenden: Implementieren Sie neue Funktionen als Microservices, trennen Sie die Präsentationskomponenten von den Geschäfts- und Datenzugriffskomponenten und konvertieren Sie vorhandene Module im Monolithen in Dienste. Mit der Zeit wird die Anzahl der Microservices wachsen und die Agilität und Geschwindigkeit Ihres Entwicklungsteams wird zunehmen.
Redaktion – Die siebenteilige Artikelserie ist nun komplett:
Sie können den kompletten Artikelsatz sowie Informationen zur Implementierung von Microservices mit NGINX Plus auch als E-Book herunterladen – Microservices: Vom Entwurf bis zur Bereitstellung . Und sehen Sie sich unsere Serie zur Microservices-Referenzarchitektur und die Seite mit Microservices-Lösungen an.
Gastblogger Chris Richardson ist der Gründer des ursprünglichen CloudFoundry.com , einer frühen Java PaaS (Platform as a Service) für Amazon EC2. Heute berät er Organisationen bei der Verbesserung der Entwicklung und Bereitstellung von Anwendungen. Außerdem bloggt er regelmäßig über Microservices unter https://microservices.io.
„Dieser Blogbeitrag kann auf Produkte verweisen, die nicht mehr verfügbar und/oder nicht mehr unterstützt werden. Die aktuellsten Informationen zu verfügbaren F5 NGINX-Produkten und -Lösungen finden Sie in unserer NGINX-Produktfamilie . NGINX ist jetzt Teil von F5. Alle vorherigen NGINX.com-Links werden auf ähnliche NGINX-Inhalte auf F5.com umgeleitet."