Blog

Refactoring zu Microservices - Einführung eines Prozessmanagers

Jan Vermeir

Aktualisiert Oktober 22, 2025
5 Minuten

Vor einiger Zeit habe ich den ersten Teil unserer Reise zur Umstrukturierung eines Monolithen zu Microservices beschrieben (siehe hier). Das war zwar ein nützlicher erster Schritt, aber es kann noch viel verbessert werden. Ich habe mich von Greg Youngs Kurs bei Skills Matter inspirieren lassen, siehe CQRS/DDD-Kurs. Da ich es für sinnvoll halte, über die Schritte nachzudenken, die man bei der Änderung der Softwarearchitektur unternimmt, habe ich mir ein paar Meilensteine gesetzt und werde über jeden berichten, wenn ich sie erreicht habe. Das erste Ziel ist die Einführung von Prozessen in unserer Domäne und zu sehen, was passiert.

Wenn Sie an den Details interessiert sind, finden Sie den Code, auf den ich mich beziehe, hier: Code auf Github. Klonen Sie das Projekt und sehen Sie sich dann den shopManager-Tag an [code language="java" collapse="false"] git clone git@github.com:xebia/microservices-breaking-up-a-monolith.git git checkout -t origin/shopManager [/code] Den Code finden Sie im Ordner services/messages. Das Problem bei unserer ersten Implementierung ist, dass ein Konzept fehlt: Es gibt keinen Begriff für einen Prozess. In früheren Lösungen war der Prozess in dem Sinne versteckt, dass ein Dienst immer dann, wenn er der Meinung war, nicht weitermachen zu können, eine Nachricht aussandte. So meldete z.B. der Shop, dass er eine Bestellung abgeschlossen hat. Diese Bestellung wurde dann von Payment und Fulfillment abgeholt. Die Zahlungsabteilung würde dem Kunden die Möglichkeit geben, zu bezahlen, während die Erfüllungsabteilung warten müsste, da sie bezahlte Bestellungen benötigt. Wenn die Zahlung abgeschlossen ist, wird eine PaymentReceived-Nachricht versendet, die es dem Fulfillment ermöglicht, weiterzumachen. Das funktioniert, aber Greg argumentiert, dass dies nur einen einzigen Prozess zulässt und dass die Lösung flexibler wäre, wenn wir einen Prozessmanager hätten, der Schritte im Prozess an verschiedene Dienste delegiert und darauf wartet, dass sie abgeschlossen werden. Das berührt einen Aspekt, der in unserer früheren Lösung nicht implementiert war: Was passiert, wenn die Zahlung zu lange dauert? In unserer ersten Lösung würde dies bedeuten, dass wir eine Datenbank mit abgeschlossenen, aber unbezahlten Bestellungen hätten. Das Problem der abgebrochenen Warenkörbe könnte durch einen Bereinigungsprozess gelöst werden, der eine Nachricht an den Kundensupport sendet und diesen auffordert, den Kunden anzurufen oder die Bestellung einfach zu löschen. Das ist der Punkt, an dem sich unsere frühere Lösung etwas eingeschränkt anfühlt. Was wäre, wenn wir mehrere Dienste bräuchten, um herauszufinden, was zu tun ist? Es scheint sinnvoll, die Prozesslogik in einem separaten Dienst zu implementieren. Daher versucht diese Version des Codes, genau das zu tun, um herauszufinden, welche Folgen das haben wird. Der Prozess sieht nun folgendermaßen aus:

[caption id="attachment_18854" align="alignnone" width="120"]Prozess mit einem Shop Manager Prozess mit einem Shop Manager[/caption]

ShopManager startet also eine Sitzung und erstellt einen Clerk. In der realen Welt wäre ein Clerk eine Person, die Ihren Einkaufswagen für Sie herumschiebt, während Sie einkaufen, die Sie zum Zahlungsterminal bringt, wenn Sie fertig sind, die den Einkaufswagen zum Fulfillment bringt, damit der Inhalt an Sie versandt werden kann, und die Sie schließlich zum Ausgang zurückbringt und zum Abschied winkt, während Sie den Parkplatz verlassen (klingt eigentlich nach einer tollen Idee). Ich werde hier nicht alle Details beschreiben, aber einige der wichtigsten Punkte hervorheben. Zum besseren Verständnis des Prozesses empfehle ich Ihnen, im Unterprojekt [scenarioTest] zu beginnen. Der [Verkäufer] fungiert als eine Art Container für alle Informationen, die wir für den Einkauf benötigen. Dies ist vergleichbar mit einem Angestellten in der realen Welt, der Ihren Einkaufswagen für Sie schiebt und dabei ein Klemmbrett bei sich trägt, das andere Informationen über Sie oder den Einkaufsprozess enthält. Der Prozess aus der Sicht des Angestellten ist in der [EventListener-Klasse] leicht zu erkennen. Er beginnt in [ClerkController], wenn die Software ein POST auf /shop/session erhält. Das führt dazu, dass eine StartShopping-Nachricht gesendet wird. Diese Nachricht wird von der Shop-Komponente aufgegriffen. Suchen Sie nach einer EventListener-Klasse im Shop-Unterprojekt. Sie können den Ablauf verfolgen, indem Sie die EventListener-Klassen und die Aufrufe von rabbitTemplate.convertAndSend() in den einzelnen Diensten überprüfen. Eine wichtige Folge dieser Architektur ist, dass wir alle Daten über den Verkäufer zwischen den Diensten weitergeben und sicherstellen müssen, dass alle Daten jedes Mal an den ShopManager-Dienst zurückgesendet werden, wenn ein Schritt abgeschlossen ist. In früheren Versionen konnten wir mit Teilimplementierungen der Domäne z.B. in den Zahlungs- oder Erfüllungsdiensten auskommen (mit dem zweischneidigen Schwert @JsonIgnoreProperties(ignoreUnknown = true)"), aber jetzt ist das nicht mehr möglich, weil wir alle Daten über den Verkäufer weitergeben. Um mir das Leben leicht zu machen, habe ich einfach alle Klassen im Domain-Paket in jeden Dienst kopiert. Ein bestimmter Dienst aktualisiert seinen Teil des Dokuments und wenn sein Unterprozess abgeschlossen ist, sendet er das gesamte Dokument zurück an ShopManager. Ich werde dies in einer späteren Version überarbeiten, um den kopierten Code loszuwerden. Die [ShopManager-Klasse] im shopManager-Projekt behält die Clerks im Auge. Jedes Mal, wenn ein Clerk erstellt und auf den Weg geschickt wird, startet ShopManager eine Sitzung, die nach einer Weile abläuft. Wenn die Sitzung abläuft und der Vorgang noch nicht abgeschlossen ist (der Clerk bleibt irgendwo im Laden stehen), räumt ShopManager das Chaos auf. In diesem Beispiel räumt er nur sein eigenes Chaos auf, aber im wirklichen Leben müsste er Bereinigungsnachrichten an jede am Prozess beteiligte Komponente senden. Durch die Zentralisierung der Implementierung des Prozesses lässt sich leicht definieren, was in Ausnahmesituationen geschehen soll. Aber noch wichtiger ist, dass es jetzt möglich ist, den Prozess auf der Grundlage externer Eigenschaften wie der Art des Kunden oder des Geschäfts zu ändern. Pläne und gute Absichten... Das Bild unten zeigt, wie weit ich bisher gekommen bin. Als Nächstes werde ich beschreiben, wie ich die meisten der Domänenklassen loswerde, die für die Verarbeitung von Clerk-Nachrichten notwendig waren, und ich hoffe, dass ich die Zeit finde, Greg Youngs Ideen über Nachrichten und Benachrichtigungen zu studieren. Es gibt noch viel mehr zu erforschen: Ich habe Docker eingeführt, um die Dienste auszuführen, was für sich genommen oder in Kombination mit einer Lösung zum gleichzeitigen Ausführen mehrerer Versionen der Software interessant sein könnte. Ein weiteres interessantes Experiment wäre es, unterschiedliche Prozesse auf der Grundlage von Merkmalen, z.B. des Kunden, zuzulassen.

Pläne

Verfasst von

Jan Vermeir

Developing software and infrastructure in teams, doing whatever it takes to get stable, safe and efficient systems in production.

Contact

Let’s discuss how we can support your journey.