Suche
BPM und Microservices

Wie lässt sich Ordnung in einen Haufen (Micro-)Services bringen?

Bernd Rücker, Daniel Meyer
Designer's Desk with Responsive Design Concept

© iStock / whitakes

Microservices sind hip oder sogar Hype. Und Martin Fowler sagt, man braucht keine Orchestrierungs-Engine in Microservice-Architekturen. Sind Workflow-Engines also überflüssig? Und wie gehen wir dann mit Geschäftsprozessen um?

Bei Microservices handelt es sich um einen Architekturstil, der in den letzten Jahren zunehmend an Popularität gewonnen hat. Ein Gesamtsystem stellt für einen Benutzer zumeist mehrere Dienste bereit. Microservices zerlegen das Gesamtsystem in einzelne Teile – eben die Microservices – die jeweils einen einzelnen Dienst fokussieren. Dies klingt ein wenig nach Serviceorientierter Architektur (SOA), unterscheidet sich aber in einem entscheidenden Punkt: in der Art und Weise, wie Microservices entwickelt, deployt und betrieben werden und wie sich die einzelnen Services in die Gesamtarchitektur eingliedern. War bei SOA die Hauptmotivation noch Wiederverwendung („built for reuse“), ist es bei Microservices viel mehr die unabhängige Austauschbarkeit einzelner Komponenten („built for replacement“).

Einzelne Microservices sollen möglichst unabhängig voneinander funktionieren. Diese Unabhängigkeit bezieht sich auf verschiedene Aspekte:

  • Unabhängiger Lebenszyklus: Microservices müssen unabhängig voneinander von verschiedenen Teams entwickelt werden können. Zudem muss es muss möglich sein, Microservices unabhängig voneinander zu starten und zu stoppen und einen Microservice unabhängig von anderen zu aktualisieren.
  • Stabile Interfaces: Microservices stellen der Umwelt stabile Interfaces bereit, die bei Updates nicht gebrochen werden dürfen. Falls inkompatible Änderungen an einer Schnittstelle notwendig werden, müssen diese durch Versionierung der Schnittstelle abgebildet werden.
  • Kommunikation: Muss ein Microservice, um seinen Dienst zu erfüllen, mit anderen Microservices kommunizieren, muss der aufrufende Service vorhersehen, dass der Kommunikationspartner nicht notwendigerweise in der Lage ist, die Anfrage sofort zu beantworten. Eingesetzte Patterns sind die Akzeptanz von partiellen Ergebnissen (ist der Kommunikationspartner nicht in der Lage, die Anfrage zu beantworten, wird dennoch weitergemacht und gegebenenfalls die Leistung eingeschränkt) oder die Kommunikation muss asynchron, meistens mithilfe von Messaging, stattfinden.
  • Robustheit und Fehlertoleranz: Einzelne Microservices müssen weiterhin funktionieren, auch wenn andere Services im Gesamtsystem gerade Probleme verursachen. In vielen Fällen ist es besser, den Fehler bis zu einzelnen Benutzern des Systems „durchschlagen“ zu lassen, als dass das gesamte System auf eine unkontrollierbare Art und Weise zusammenbricht.
  • Lokale Datenhaltung: Oft halten Microservices sogar lokale Kopien von Daten, die sie benötigen, um ihren Dienst zu erfüllen.
  • Unabhängige Skalierbarkeit: Nicht jeder Microservice benötigt die gleichen Ressourcen. Es muss möglich sein, jedem Service die von ihm benötigten Ressourcen zur Verfügung zu stellen, ohne dadurch andere zu beeinflussen.

Die sechs hier genannten Punkte sind als Richtlinien zu verstehen. Nicht jeder Microservice muss notwendigerweise jedes Kriterium erfüllen oder den hier genannten Herausforderungen auf die gleiche Art und Weise begegnen.

Lang laufende Prozesse

Relativ gut verstanden werden einzelne Interaktionen zwischen Microservices, wobei asynchrones Messaging favorisiert wird. Lang laufende Prozesse bestehen allerdings meist aus mehr als einer Interaktion – und es sind mehr als ein System oder Mensch beteiligt. Im Kontext solcher Prozesse ergeben sich interessante Probleme. Betrachten wir ein Beispiel: In einem Webshop wird eine neue Bestellung erzeugt. Die Abwicklung dieser Bestellung soll folgenden Anforderungen genügen:

  • Nach dem Eingang der Bestellung muss die Ware im Lagersystem reserviert werden, bis die Bezahlung getätigt wurde.
  • Wenn die Bezahlung fehlschlägt, hat der Benutzer zwei Tage Zeit, die Bezahlung zu wiederholen, danach wird die Ware wieder freigegeben.
  • Ist die Bezahlung erfolgreich, wird der Versand vorbereitet.
  • Bevor der Versand vorbereitet wurde, hat der Benutzer die Möglichkeit, die Bestellung zu stornieren.

Wir versuchen zunächst, diese Anforderungen rein auf Basis von asynchronem Nachrichtenaustausch zu implementieren. Der Webshop schickt eine Nachricht an den Lagerservice. Dort wird die in der Bestellung enthaltene Ware reserviert. Nachdem dies passiert ist, schickt der Lagerservice eine Nachricht an den Bezahlservice. Dieser versucht, die Kreditkarte des Kunden zu belasten. Gelingt dies, wird eine Nachricht zurück an den Lagerservice geschickt, um die Ware auszubuchen und den Versand vorzubereiten.

Schlägt die Bezahlung fehl, wird eine Nachricht an den Kunden geschickt und dieser muss die Daten zur Bestellung aktualisieren. Will der Kunde die Bestellung stornieren, könnte es schwierig werden, diese Nachricht zuzustellen – da man nicht weiß, welchen Status die Bestellung gerade hat. Daher geht die Nachricht vermutlich ebenfalls initial an den Lagerservice. Ist der Versand bereits vorbereitet, wird die Stornierung abgelehnt. Ansonsten wird die Bestellung als storniert vermerkt und eine Nachricht an den Bezahlservice geschickt, den Bestellbetrag (falls bereits abgebucht) gutzuschreiben.

Abb. 1: Services kommunizieren miteinander – der Status des Prozesses ist nicht sichtbar

Abb. 1: Services kommunizieren miteinander – der Status des Prozesses ist nicht sichtbar

Die resultierende Implementierung ist in Abbildung 1 visualisiert und hat verschiedene Nachteile:

Punkt-zu-Punkt-Verbindungen: In unserem Beispiel muss ein Service wissen, an welchen anderen Service die nächste Nachricht geschickt wird. Die initiale Nachricht geht zum Beispiel vom Webshop an den Lagerservice. Der Webshop muss also wissen, dass der Lagerservice der erste ist. Das Gleiche gilt für die zweite Nachricht vom Lager an den Bezahlservice. Wenn jeder Service aber wissen muss, was in Bezug auf die aktuelle Interaktion der jeweils nächste Schritt ist, wird es schwer, den Gesamtablauf zu verändern oder auch nur zu erkennen. Soll vor der Reservierung der Ware im Lager zuerst eine andere Aktion von einem anderen System durchgeführt werden, muss die Logik im Webshop angepasst werden, obwohl sich für diesen eigentlich nichts verändert hat. Fällt einer der Services in der Kette aus, kann dies zwar durch den asynchronen Nachrichtenaustausch abgefedert werden, allerdings ist es jetzt schwer, auf den Ausfall zu reagieren. Eine gute Option wäre es beispielsweise, dem Benutzer eine Nachricht zu schicken, mit dem Hinweis, dass sich die Bestellung verzögert. Stattdessen stauen sich nicht zustellbare Nachrichten in einer Queue.

Keine Lösung für Reihenfolge, gegenseitigen Ausschluss, Synchronisation und Timeouts: In unserem Beispiel existieren viele mögliche Race Conditions. Die Bearbeitung einer Bestellung besteht zum Beispiel aus den drei Schritten Ware reservieren (Lagerservice), Bezahlung (Bezahlservice), Versand vorbereiten (Lagerservice). Der Fortschritt wird mithilfe von Nachrichten durch diese Pipeline abgebildet. Für die Stornierung können wir Rückwärtskompensation anwenden: In umgekehrter Reihenfolge zum normalen Fortschritt wird eine Abbruchnachricht durch die Pipeline geschickt. Jeder Service, der diese Nachricht erhält, macht den Vorgang – falls notwendig und möglich – rückgängig und reicht die Nachricht weiter. Dies ist bereits kompliziert genug, hat aber noch ein zusätzliches Problem: Die Fortschrittsnachricht und die Kompensationsnachricht können sich „treffen“. Falls also beide Nachrichten gleichzeitig bei einem Service eintreffen, muss dieser die Nachrichten selbst synchronisieren und den gegenseitigen Ausschluss (Mutual Exclusion) garantieren. Durch den asynchronen Nachrichtenaustausch kann es auch passieren, dass die Nachrichten sich nicht „treffen“: Die Nachrichten sind gleichzeitig in einer Queue und laufen aneinander vorbei. Auch hier gibt es Strategien, diese muss man jedoch kennen und implementieren.

Fehlender Zustand einer Bestellung: Interessiert man sich für den Zustand einer einzelnen Bestellung, müssen verschiedene Services gefragt werden. Den Zustand einer Bestellung zu kennen, kann aus unterschiedlichen Gründen wichtig sein, z. B. um auf dieser Basis zu entscheiden, ob die Bestellung storniert werden kann und wie das passiert. Auch von Seiten Monitoring und Operations gibt es entsprechende Anforderungen.

Steigende Komplexität: In unserem vereinfachten Beispiel sind nur wenige Services beteiligt. In der Realität werden es vermutlich mehr sein und die Interaktion damit deutlich komplexer werden. Dann wird es zunehmend schwerer, den Prozess als Ganzes zu verstehen, oder gar basierend auf neuen Anforderungen zu ändern.

Message-Router, ESB und Eventbus

Diesen Problemen lässt sich mit klassischen Ansätzen begegnen. Man kann z. B. die Punkt-zu-Punkt-Verbindungen durch einen Message-Broker mit Routingfähigkeiten auflösen, der heute oftmals in ESB-Produkten integriert ist. Die Systeme schicken sich dann keine direkten Nachrichten mehr, sondern kommunizieren über den Message-Broker, der das Zielsystem basierend auf hinterlegten Routen ermittelt.

Alternativ kann man einen Eventbus einführen und Topics anstelle von Queues verwenden, sodass Nachrichten als Broadcast in die Welt gehen und Microservices selbst entscheiden, welche Nachricht sie konsumieren wollen – mit spannenden Fragestellungen dazu, was passieren soll, wenn niemand eine Nachricht in angemessener Zeit verarbeitet.

So oder so entsteht der Prozess aber erst durch das Zusammenspiel verschiedener Services zur Laufzeit, was als Choreografie bezeichnet wird. Muster wie Synchronisation und gegenseitiger Ausschluss müssen in den Services selbst gelöst werden, und es ist sehr schwierig, den Zustand einer einzelnen Bestellung transparent zu machen. Daher ist es ungünstig, lang laufende Prozesse nur mithilfe von Messaging zu implementieren, selbst unter Verwendung eines Message-Brokers, ESB oder Eventbus.

Weiterer Lösungsansatz: Serviceschnitt

Alternativ kann man die Services anders „schneiden“, z. B. einen eigenen Bestellservice einführen, der die Reservierung der Ware sowie die Möglichkeit der Stornierung in sich kapselt. Der Microservice kann eine lokale Kopie der Lagerbestandsdaten vorhalten, die periodisch mit den Lagerdaten abgeglichen wird. Es kann jetzt lokal entschieden werden, ob die Ware verfügbar ist, und wie Stornierungen zu behandeln sind. Bestellungen können nach Abwicklung der Bezahlung an das Lagersystem übergeben werden. Hat der Bestellservice eine falsche Entscheidung getroffen, weil er durch die Replikation auf veralteten Daten gearbeitet hat, muss dies im Ausnahmefall fachlich gelöst werden.

Im Wesentlichen wurde die Komplexität rund um Reservierung und Bezahlung und die hiermit konkurrierende Stornierung in einem einzelnen Service gekapselt, zu dem Preis, dass die Lagerdaten repliziert werden müssen. Dies ist sicher eine sinnvolle Reduktion der Gesamtkomplexität, allerdings bleibt noch genügend Prozessfluss übrig, der dennoch abgebildet werden muss. Und dabei muss sich der Serviceentwickler selbst um Race Conditions kümmern, wodurch schnell eine eigene Workflow-Engine gebaut wird.

Interessant ist es, diesen Gedanken weiterzudenken, denn es wird sehr viel Logik in den Bestellservice wandern, wodurch sich wieder ein Monolith bauen lässt. Es ergibt sich ein Spannungsverhältnis zwischen dem Bedürfnis, einzelne Services autonom Entscheidungen treffen zu lassen, dem Bedürfnis, Microservices zu fokussieren, um die einzelnen kleineren Teile getrennt zu entwickeln, testen und updaten zu können sowie dem Bedürfnis, einen sinnvollen Geschäftsprozess umzusetzen.

Der aus unserer Sicht einzig sinnvolle Ausweg ist – auch in Zeiten von Microservices – eine Workflow-Engine.

Business Process Management (BPM) und Workflow-Engines

Workflow-Engines dürften den meisten Entwicklern mittlerweile bekannt sein. Sie erlauben es, lang laufende Prozesse mit komplexer Ablauflogik grafisch zu modellieren und das Modell anschließend direkt auszuführen. Abbildung 2 zeigt unseren Bestellprozess im BPMN-2.0-Standard modelliert.

Abb. 2: Der Bestellprozess als BPMN-2.0-Modell

Abb. 2: Der Bestellprozess als BPMN-2.0-Modell

Das Modell liegt als XML-Datei vor und kann von unterschiedlichen Tools verstanden werden. Neben Modellierungswerkzeugen, die zum Erstellen der Diagramme genutzt werden, gibt es vor allem auch Workflow-Engines, die die Modelle ausführen. Die grafische Darstellung des Prozesses als Bild ist dabei entscheidend: Sie erlaubt es sowohl Programmierern als auch Nichtprogrammierern, den Ablauf zu verstehen und bei dessen Erstellung sowie anderen Phasen des Softwarelebenszyklus mitzureden. BPM betrachtet nicht nur den technischen Kontext, sondern beschäftigt sich mit der Zusammenarbeit von Menschen in unterschiedlichen Rollen rund um den Lebenszyklus eines Prozesses.

Explizite statt implizite Prozesse

Die Workflow-Engine erlaubt es, die bisher in der Kommunikation implizit vorhandenen Prozesse explizit zu machen und sogar grafisch darzustellen. Dies bringt eine Reihe von Vorteilen:

Explizite Koordinationslogik: Die Koordinationslogik wird aus den Microservices „herausgezogen“ und in einem BPMN-Modell gekapselt. Dadurch werden die Punkt-zu-Punkt- Verbindungen aufgelöst: Ein Microservice, der an einem lang laufenden Prozess teilnimmt, muss nicht selbst wissen, welches System das nächste ist, sondern dieses Wissen ist im Prozess gekapselt. Soll der Prozess angepasst werden, müssen die beteiligten Microservices nicht notwendigerweise verändert werden.

Zustand lang laufender Prozesse: In einer Workflow-Engine wird jede Ausführung des Prozesses Prozessinstanz genannt – außerdem wird der Fortschritt verfolgt, und es können Daten dazu gespeichert werden. Diese explizite Abbildung des Interaktionszustands als Objekt ist die Basis für den Nutzen, den die Workflow stiftet.

Transparenz: Der Zustand einer Prozessinstanz kann sehr einfach von der Workflow-Engine angerufen werden. Monitoring kann direkt auf den Diagrammen stattfinden.

Nachrichtenkorrelation und -koordination: Mehrere Nachrichten, die zum gleichen Vorgang gehören, werden in der Prozessinstanz nun zusammengeführt. Konflikte werden im Moment der Korrelation mit Unterstützung der Workflow-Engine gelöst. Außerdem fällt es der Workflow-Engine leicht, zu entscheiden, was auf Basis des aktuellen Fortschritts und der eingehenden Nachrichten als Nächstes passieren muss. Kommunikationsmuster wie „Reihenfolge von Nachrichten“, „Synchronisation von Nachrichten“, „Warten auf Nachrichten mit Timeouts“ sowie „gegenseitigen Ausschluss (Mutual Exclusion) von Nachrichten“ sind in BPMN direkt gelöst (Abb. 3).

Abb. 3: Lösung verschiedener Kommunikationsmuster in BPMN 2.0

Abb. 3: Lösung verschiedener Kommunikationsmuster in BPMN 2.0

Zusammengefasst ist es also durchaus sinnvoll, eine Workflow-Engine in einer Microservice-Architektur zu verwenden. Eventuell ist es hilfreich, in diesem Fall eher von Geschäftsprozessen und Workflows zu sprechen, als von „Service Orchestration“, da dieser Begriff oft negativ konnotiert ist (Kasten: „Composite Services vs. lang laufende Prozesse“).

Composite Services vs. lang laufende Prozesse

In SOA werden oft einzelne Services mithilfe zustandsloser Orchestrierung zu Composite Services komponiert, dazu kamen in der Vergangenheit Sprachen wie BPEL zum Einsatz. Ein typisches Beispiel: Es wird ein Datenobjekt benötigt, das Daten komponiert, die bereits von unterschiedlichen Services bereitgestellt werden. Dies wird abgebildet, indem ein BPEL-Prozess erstellt wird, der die einzelnen Services nacheinander aufruft und die Daten aggregiert. Dieses Muster widerspricht allerdings der Microservice-Philosophie: Ist einer der aufgerufenen Services nicht verfügbar, kann der Composite Service nicht arbeiten. Die Microservice-Antwort auf diese Problemstellung könnte eher Datenreplikation sein: In einer eigenen Datenbank wird das komposite Datenobjekt vorgehalten und regelmäßig aktualisiert.

In lang laufenden Prozessen werden ebenfalls mehrere Services aufgerufen – jedoch mit einem völlig anderen Ziel: Es sollen nicht Daten aggregiert werden, sondern ein aus mehreren Schritten bestehender Vorgang – meist ein Geschäftsprozess – abgebildet werden. In einem solchen Prozess werden oftmals die Ergebnisse aus einem Schritt in den darauffolgenden Schritten benötigt bzw. es muss gewartet werden, bis ein Schritt erfolgreich (oder nicht erfolgreich) abgeschlossen werden konnte, bevor es weitergeht. In der Konsequenz können (und müssen) die einzelne Schritte oft asynchron ausgeführt werden – im Gegensatz zu Composite Services, wo eine Kaskade von synchronen Serviceaufrufen benötigt wird.

In der aktuellen Diskussion um Orchestrierung in der Microservices-Community werden meist Composite Services gemeint, die Orchestrierung als Ganzes allerdings verteufelt. Wir vermuten, dass in diesem Kontext auch Martin Fowlers Ausspruch entstanden ist. Zustandsbehaftete Orchestrierung von Geschäftsprozessen ist aber etwas anderes.

Workflow-Engines und Microservices

Beim Einsatz einer Workflow-Engine gibt es in Kombination mit Microservices eigene Fragestellungen und Herausforderungen. Wir möchten zwei herausgreifen: Kommunikation der Engine mit den Microservices sowie Betrieb einer oder mehrere Engines in einer Microservices-Architektur.

Workflow-Engine pro Microservice vs. zentrale Engine

Microservices sollten möglichst unabhängig voneinander sein, weswegen Sie typischerweise alle notwendigen Libraries selbst mitbringen und hochfahren. Das wirft sofort die Frage auf, ob jeder prozessartige Microservice – in unserem Beispiel der Bestellservice – eine eigene Workflow-Engine hat. Dies ist mit „Embeddable Engines“, die als Library in die eigene Anwendung eingebettet werden, technisch möglich.

Grundsätzlich spricht nichts dagegen, einzelnen Microservices freizustellen und eine eigene Engine einzubetten. Allerdings gibt es zwei große Fragestellungen, auf die man eine Antwort finden muss:

  • Wie kann man viele Engines überwachen bzw. ein übergreifendes Monitoring realisieren? In der Vergangenheit haben wir dazu vor allem mit zentralen Audit-Datenbanken gearbeitet, vergleichbar mit Lösungen, die alle Logdaten zentral zusammenziehen. Der Eingriff in den Prozessablauf ist aber schon schwieriger, da nun die richtige Engine adressiert werden muss.
  • Wie wird eine einheitliche Aufgabenliste für den Benutzer realisiert? Der Benutzer möchte meist eine Sicht auf alle seine Aufgaben – ganz gleich, von welchem Service sie erzeugt werden. Dies ist nicht einfach zu lösen und involviert schnell einen eigenen Aufgaben-Microservice, der die Architektur weiter verkompliziert und Abhängigkeiten schafft.

Die Alternative ist ein zentraler Workflow-Microservice, der die Steuerung aller Geschäftsprozesse übernimmt. Dieser kann eine zentrale Aufgabenliste bereitstellen sowie die Anlaufstelle für Monitoring und Operations sein. Auch wenn diese Variante gerne als zu „SOA-artig“ und veraltet abgestempelt wird, hat sie doch viele Vorteile: Allen voran der Charme einer einfachen Lösung, die in der Praxis gut funktioniert.

Hat man jedoch Anforderungen, die einem zentralen Workflow-Microservice entgegensprechen – beispielsweise dass unterschiedliche Prozesse unabhängig voneinander gestartet, skaliert oder angepasst werden müssen – kommt die Variante möglicherweise nicht in Frage. An dieser Stelle möchten wir daher auf einen interessanten dritten Weg hinweisen, den wir am Beispiel der Camunda-BPM-Plattform beschreiben.

Abb. 4: Viele Embedded Engines bilden eine logische Engine

Abb. 4: Viele Embedded Engines bilden eine logische Engine

Camunda unterstützt so genannte heterogene Cluster – man kann also Cluster aufbauen, wobei nicht auf jedem Knoten die gleichen Prozesse (wir sprechen von „Prozessanwendungen“) verfügbar sind. Clustering in Camunda funktioniert, indem mehrere Engines auf der gleichen Datenbank operieren – mehr ist nicht notwendig. Es können also beliebige Engines – auch Embedded in Microservices – gestartet werden, die aber die gleiche Datenbank verwenden. Dadurch entsteht eine große logische Workflow-Engine, in der jeder Microservice aber nur ihn betreffende Prozesse anfasst (Abb. 4).

Es ist hier sogar out of the box möglich, dass Subprozesse von einem Microservice gestartet werden, die dann ein ganz anderer Microservice abarbeitet. Eine zentrale Aufgabenliste ist ebenfalls gegeben, auch wenn in der konkreten Ausprägung geklärt werden muss, wie und wo Oberflächen für Aufgaben geladen und angezeigt werden.

Dieser Mittelweg vereint die Vorteile der isolierten Umgebung mit den Vorteilen einer zentralen Engine, er wurde auch bereits erfolgreich im Kundenprojekt eingesetzt. Die zentrale Datenbank ist dabei aus architektonischer Sicht nicht „schlechter“ als eine zentrale Messaging Infrastruktur, weswegen wir dies gut akzeptieren können.

Pull vs. Push: External-Tasks-Pattern

Eine Workflow-Engine ist gewohnt, selbst aktiv zu agieren. Das hat den Nachteil, dass die Engine die Endpunkte kennen und erreichen muss. Nun könnte man dies über eine Registry oder auch einen Message-Router lösen, allerdings befreien sich mehr und mehr Projekte mit Workflow-Engine von der Notwendigkeit, zusätzlich eine Messaging-Infrastruktur aufzubauen, die meist recht komplex ist. Auch soll das Gesamtsystem möglichst flexibel agieren können, z. B. will man neue Microservices ohne Änderung eines zentralen Message-Routers oder einer Registry in Betrieb nehmen.

Die Workflow-Engine hat außerdem keine Sensibilität für die Überlastung einzelner Services. Im Gegenteil: Sie versucht Services wiederholt aufzurufen, wenn diese nicht verfügbar sind. Was grundsätzlich ein sehr sinnvolles Feature ist, widerspricht dem Prinzip des Architekturstils mit Microservices. In Fehlersituationen kommt es zu unnötiger Zusatzbelastung auf einen ohnehin schon unter Stress stehenden Service. Sobald der Service wieder normal arbeitet oder auch mit zusätzlichen Ressourcen ausgestattet wurde, lastet die Engine ihn evtl. nicht mehr aus, da sie nicht weiß, dass sie jetzt mehr Anfragen stellen darf.

Daher hat sich in der Projektpraxis ein Muster herauskristallisiert, das alternativ zu aktiven Serviceaufrufen zum Einsatz kommen kann: die External-Task. Die Grundidee ist simpel, wir behandeln den Microservice wie einen Menschen und legen ihm eine Aufgabe – die External-Task – bereit, die er selbst abrufen und abarbeiten kann, wann er möchte. Gibt es dabei viele parallele Arbeiter, können diese Aufgaben parallel abarbeiten. Die Arbeiter können für die Engine transparent hoch- und runtergefahren – also skaliert – werden. Damit wird man auch den Anforderungen an eine Microservices-Architektur gerecht: Services können sich über Schnittstellen Aufgaben abholen (dies ist prinzipiell vergleichbar mit Nachrichten aus einer Queue), bearbeiten und Bescheid geben, wenn dies passiert ist. Wenn der Service nicht verfügbar ist, sammeln sich die Aufgaben in der entsprechenden Liste. Wird der Service hochskaliert, können grundsätzlich beliebig viele Aufgaben gleichzeitig abgearbeitet werden.

Natürlich sollte die Engine noch ein paar Dinge zusätzlich tun. Sie muss vor allem überwachen, dass kein Prozess zu lange auf einen Service wartet und dadurch liegen bleibt. Auch muss es Locking-Mechanismen geben, sodass immer nur ein Arbeiter die Aufgabe übernehmen kann. Trotzdem muss bei einem Absturz dieses einen Arbeiters sichergestellt sein, dass jemand anderes einspringt.

Diese Anforderungen hat eine moderne Engine intern für normale Servicetasks bereits abgebildet. In Camunda BPM wäre das Anbieten spezieller Features für diesen Use Case daher denkbar einfach. Allerdings müssen Sie nicht auf Ihren Hersteller warten, sondern können auch sofort loslegen, indem Sie die bestehende Funktionalität rund um Taskmanagement nutzen und leicht erweitern. Wir haben Ihnen dies beispielhaft als lauffähigen Prototypen inklusive Quellcode für Camunda BPM bereitgestellt.

Mit diesem Pattern drehen wir die Kommunikationsrichtung um – die Workflow-Engine ruft nicht mehr den Service aktiv auf („Push“) sondern der Service holt sich External-Tasks als Arbeitspakete selbstständig ab („Pull“). Dabei kann am einfachsten über Webserviceschnittstellen interagiert werden (z. B. REST). Wir können uns aber auch ein API vorstellen, das von der konkreten Umsetzung abstrahiert, und das idealerweise in verschiedenen Technologien einsetzbar ist. Listing 1 zeigt Pseudocode für eine mögliche Variante in Java.

CamundaClient client = CamundaClient.configure()
.url("http://localhost:8080/engine-rest")
.username("hi").password("ho")
.create();

client.onExternalTask("lagerservice:ware/reservieren", new ExternalTaskDelegate() {
    public void execute(ExternalExecution ex) {
      VarialesMap vars = ex.getVariables(Arrays.asList("a", "b", "c"));
      //...
      Customer c = new Customer(...);
      ex.setVariable("myVar1", jsonValue(c))
      ex.complete();
    }
});

// Falls wir warten möchten:
client.closeFuture().get();

Fazit

Wir glauben, dass gerade in einer Microservices-Architektur eine Workflow-Engine und BPMN einen festen Platz haben sollten. Heute wird gern Gegenteiliges behauptet, ohne die Frage zu beantworten, wie Geschäftsprozesse ansonsten als komplexe Interaktion einzelner Services entstehen können. Dies resultiert sicher auch daraus, dass Orchestrierung von Geschäftsprozessen (mit Zustand!) in der Vergangenheit nicht sauber zu Composite Services (ohne Zustand!) abgegrenzt wurde. Transparenz und Business-IT-Alignment sind wichtige Ziele, die man auch mit modernen Architekturmustern nicht über Bord werfen sollte. Es gibt heute BPM-Produkte, die flexibel genug sind, um die unterschiedlichsten Anforderungen abzudecken und Microservices nicht im Weg stehen. Die Funktionalität und das Erfahrungswissen dieser Produkte kann sich also durchaus lohnen.

Geschrieben von
Bernd Rücker
Bernd Rücker
Bernd Rücker hat vor camunda BPM aktiv an der Entwicklung der Open Source Workflow Engines JBoss jBPM 3 und Activiti mitgearbeitet. Bernd begleitet seit 10 Jahren Kundenprojekte rund um BPM, Process Engines und Java Enterprise. Er ist Autor mehrerer Fachbücher, zahlreicher Zeitschriftenartikel und regelmäßiger Sprecher auf Konferenzen.
Daniel Meyer

Daniel Meyer ist Project Lead und führender Architekt der Camunda BPM Platform. Er denkt pausenlos über BPM in modernen Architekturen und die damit verbundenen Herausforderungen nach.

Mail: daniel.meyer@camunda.com

Kommentare
  1. Wer Microservices richtig macht, braucht keine Workflow Engine und kein BPMN - codecentric Blog : codecentric Blog2015-09-01 08:00:20

    […] Rücker und Daniel Meyer schreiben im Fazit von „Wie lässt sich Ordnung in einen Haufen (Micro)Services bringen“, […]

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht.