Microservices verbinden in Datenflüssen

Mir hat der Blogartikel von Tobias Flohre gut gefallen, in dem er die Kommunikation zwischen Microservices mit asynchronen Events beschreibt. Er tut das als Gegenentwurf zu einer Darstellung mit BPMN und einer Implementation auf der Basis einer Workflow-Engine.

Entkoppeln mit Events

Gut gefallen hat mir der Artikel, weil Tobias damit eine Lanze für das Principle of mutual Oblivion (PoMO) bricht. Es besagt, dass Funktionseinheiten in Software nicht funktional abhängig von einander sein sollen. Logik in der einen sollte Logik in der anderen nicht kennen, nicht nutzen. Jede Funktionseinheit sollte vielmehr quasi selbstvergessen "nur ihr Ding machen" und sich nicht um die Umwelt scheren. So tut das in einem Organismus jede Zelle; warum also nicht auch jede Funktionseinheit in einer Software? (Das ist aus meiner Sicht sogar, was Alan Kay ursprünglich unter Objektorientierung verstanden hat. Hier dazu eine Artikelserie von mir: OOP as if you meant it.)

Ich schreibe hier bewusst "Funktionseinheit", weil ich auf konzeptioneller Ebene spreche. Eine Funktionseinheit kann im Code eine Funktion oder eine Klasse... oder eben auch ein Microservice sein. Eben ein Modul als Träger von Verhalten herstellender Logik.

Eine PoMO-konforme Funktionseinheit bekommt von irgendwoher "etwas", arbeitet daraufhin und liefert anschließend "etwas anderes" ab. Woher "etwas" kommt, wohin "etwas anderes" geht, das ist der Funktionseinheit einerlei. Das mag seltsam klingen – doch Sie sind damit ganz vertraut. Jede Funktion T f<S,T>(S s) arbeitet so.

Hier ein Beispiel für eine solche Funktionseinheit aus Tobias' Artikel:

Der Microservice WarenReservierungService konsumiert/abonniert zwei Events (links) und produziert/sendet zwei Events (rechts).

Weiß der Microservice, woher die Events kommen und wohin sie gehen? Nein. Er kennt seine Position in einem Größeren nicht, dessen Teil er ist.

Das gefällt mir an Tobias Ansatz.

Wer zuviel entkoppelt, verliert den Anschluss

Aber für mein Gefühl kippt er damit das Kind mit dem Bade aus. Tut mir leid.

Denn wenn man auf solcher Sichtweise in der Kommunikation mit Menschen besteht, dann werden Microservice-Systeme nicht wirklich leichter zu verstehen.

Hier drei weitere Microservice-Beispiele aus dem Artikel. Sie müssen nicht verstehen, was die im Einzelnen tun:

Fragen Sie sich vielmehr: Haben Sie eine Idee, was die im Gesamten tun? Sehen Sie einen Zusammenhang?

Nein. Das können Sie ja nicht, weil es kein Ganzes gibt. Jeder Microservice wird PoMO-betont allein dargestellt, also bewusst ahnungslos, zusammenhangslos.

Das ist ja einer von Tobias Punkten: Vergesst BPMN für Microservice-Systeme!

Aber mit BPMN würde ein Zusammenhang an den Anfang gestellt: ein Geschäftsprozess. Hier das Beispiel aus seinem Artikel:

In so einem Prozessdiagramm können Sie verfolgen, wie "die Dinge laufen sollen". Oder besser, wie die Dinge fließen sollen.

Und warum denn auch nicht?

Was wird besser, wenn der Datenfluss durch Event-"Schweißnähte" ersetzt wird? Hier Tobias Überblicksdiagramm:

Ist da jetzt Klarheit gewonnen? Ein Ganzes – aber ohne erkennbare Ausrichtung. Pfeile zeigen kreuz und quer. Events (Daten) dominieren, Aktivitäten (Microservices) sind zwischengestreut.

Sorry, das verwirrt mich eher, denn dass es mir nützt.

Datenflüsse zwischen Microservices

Was ist denn aber eigentlich Tobias Problem mit BPMN?

Dass Daten fließen? Aber Events sind doch auch Daten. Im BPMN-Diagramm sind ja nicht mal Daten erwähnt. Wer Events an die Pfeile notieren mag, kann das aus meiner Sicht tun. Oder wird man dann imkompatibel mit der heiligen BPMN?

Ist das Problem also vielleicht eher eine wuchtige Notation? BPMN 2.0 klingt ja fast wie UML 2.0. Wer überblickt das schon? Das Gefühl kenne ich – und benutze deshalb BPMN selten. Es ist für mich keine Notation, in der ich Entwürfe beschreibe. BPMN gehört für mich eher in die Analyse und vor allem Dokumentation. Für den Entwurf ziehe ich leichteres Gepäck vor; da will ich ja zügig vorankommen bei der Exploration von Lösungsansätzen.

Hier mein Vorschlag für Tobias Microservice-Architektur notiert mit einfachsten Mitteln als "Datenflussdiagramm":

Ich habe seine Microservice- und Eventnamen abgekürzt für mehr Fokus auf den Fluss, das Ganze, den Zusammenhang.

Fett hervorgehoben ist der Eventverlauf (statt Datenfluss) für den happy day Fall: alles läuft prima mit der Bestellung. Rote Events signalisieren unerwartete Ereignisse, Ausnahmen. Violette Events beschreiben Aufforderungen zur Kompensation.

Was ist falsch daran, einen solchen knappen Überblick für das Ganze des Prozesses, um den es unstrittig geht, haben zu wollen? So nützlich die "zusammenhanglose" Darstellung im Sinne des PoMO sein mag wie hier z.B. als Microservice-Event-Matrix – sie kann keinesfalls genügen. Denn ihre Interpretation, d.h. das Reverseengineering des darin codierten Prozesses im Kopf macht viel Mühe und ist fehleranfällig. Damit lassen sich Modelle nur schwer von Mensch zu Menschn transportieren:

Und darin stecken doch auch die Linien des Datenflussdiagramms. Sie verlaufen vertikal in einer Event-Spalte von w-Microservicequellen zu r-Senken. Und Tobias' Einzeldarstellungen finden sich horizontal pro Zeile jedes Microservice'.

Da kann man entdecken, wer ein rechter Eventspringbrunnen :-) ist, z.B. GES. Oder wo Events vor allem "hingesogen" werden, z.B. BAS.

Wenn ich im Fehlerfall oder als neuer Kollege aber verstehen will, wie so ein Microservice-System funktioniert... dann möchte ich ein Datenflussdiagramm (oder Eventverlauf) sehen.

Wie gesagt, es reicht doch eines wie oben gezeigt. Darin sehe ich die Services, darin sehe ich auch noch, welche ein Frontend haben. Und ich sehe, welche Events es gibt und wie sie fließen. Wer erzeugt welche, wer konsumiert welche. So kann ich mir Gedanken über Kausalitäten machen.

Und was sagt das über die Implementation aus? Nichts.

Müssen deshalb Microservices wie WRS und GES einander kennen, nur weil zwischen ihnen ein Pfeil läuft? Natürlich nicht. Ein Pfeil zeigt ja nur an, dass Daten fließen. Es ist kein UML-Abhängigkeitspfeil, sondern ein Flusspfeil. Kennt die Quelle das Meer? Und doch können wir einen Fluss als Pfeil von ihr dorthin abstrahieren.

Muss unter einer Implementation solch eines Diagramms ein ESB liegen, eine Workflow-Engine oder auch nur irgendeine spezielle Technologie? Nein. Das kann jeder entscheiden, wie er mag – im Rahmen des PoMO. Und ich glaube, das wollte Tobias rüberbringen: "Leute, macht eure Microservices nicht von einander abhängig!"

Ich würde sogar noch weitergehen und sagen: Man sollte die Logik eines Microservice, also seine Domäne so formulieren, dass schon die nicht weiß, ob sie überhaupt in einem Microservice aufgehängt ist. Das könnte in C# so aussehen:

public class Warenreservierung {
  public void Verarbeiten(BestellungErzeugtEvent e) {
    ...
  }
 
  public event Action<WarenReserviertEvent> WennWarenReserviert;
  public event Action<WarenNichtReserviert> WennReservierungFehlgeschlagen;
}

Das Microservicespezifische ist dann drumherum zu wickeln. Es liegt auf einem anderen Stratum als die Domäne im Sinne eines stratified design oder einer Onion Architecture des einzelnen Microservice. Sie könnten ihr ganzes Microservice-System also auch zuerst in einem Prozess asynchron laufen lassen, indem Sie solche Domänenlogik in Actors aufhängen.

Die Masterfrage ist am Ende also natürlich: Wie kann die konzeptionelle Ignoranz der Microservices gegenüber einem Prozess, also dem, was upstream bzw. downstream passiert, auch in der technischen Umsetzung erhalten werden?

Dazu mögen technologisch und produktbezogen Kundigere etwas sagen.

Hier nur noch ein Hinweis: Die Linie zwischen entkoppelnder PoMO-Konformität und koppelnder funktionaler Abhängigkeit ist manchmal fein. Bei der obigen Beispielklasse würde sie z.B. überschritten, wenn der erste Event so definiert wäre:

public event Action<WarenReserviertEvent> GeldEinziehenService;

Sehen Sie den Unterschied?

Nur der Event-Name lautet anders. Er bezieht sich nicht mehr auf den Service selbst, ist nicht nach innen gerichtet, sondern nimmt Bezug auf einen anderen Service, ist also nach außen auf einen konkreten downstream Prozess gerichtet.

So würde der Microservice in einem bestimmten Kontext gezwängt. Sein Kontrakt würde über die Events hinausgehen, die er verarbeiten kann und die er erzeugt. Das wäre eng koppelnd, unnötig eng.

Also: Vorsicht bei der Implementation von Microservice-Systementwürfen! Da stimme ich Tobias Flohre ganz zu.

Doch deshalb würde ich eben keine Technologie ausschließen – soweit sie PoMO-konform implementieren kann. Und ich würde vor allem nicht ausschließen, in Datenflüssen zu denken und Microservice-System in der Weise auch (visuell) zu kommunizieren.

Wenn Microservice-Systeme keine Prozesse realisieren sollen, weiß ich auch nicht. Und Prozesse sind als Prozesse darzustellen. Alles andere erzeugt einen Impedanzunterschied zwischen Kopf und Code, der nur Schmerzen bereitet.