EventBus und Microservices - a Case Study: Part VI
The bigger picture ... inklusive Fehlern und anderen Unannehmlichkeiten.
Die Artikel dieser Reihe
Einleitung
Software is not an end in itself: it supports your business processes and makes customers happy. If software is not running in production it cannot generate value. Productive software, however, also has to be correct, reliable, and available.
When it comes to resilience in software design, the main goal is build robust components that can tolerate faults within their scope, but also failures of other components they depend on.
—codecentric
Über vieles wurde diskutiert. Event-Nachrichten, Transaktionen, Technologien, usw. Bislang haben wir uns die Sache doch ein wenig einfach gemacht und nur die Kommunikation zwischen lediglich zwei Applikationen / Services betrachtet und weiterhin dahingehend idealisiert, dass schon alles irgendwie funktionieren wird; Zeit dieses anzugehen.
Zur Erinnerung: das vereinfachte Gesamtsystem
things fail all the time
An event-driven architecture (EDA) brings changes to the way we approach error handling. When using the more commonly adopted synchronous API, we have immediate indications of problems that occurred. With the asynchronous route, error detection becomes trickier and is either neglected or not properly addressed.
Unsere ersten Überlegungen gingen dahin, was geschieht eigentlich mit Fehlern im Prozess? Stellen wir uns der Einfachheit halber vor, die Reaktion des Verification-System erfolgt nicht aufgrund eines internen Fehlers (bspw. die ID des Kunden wurde falsch übermittelt so das der REST-Aufruf zur Detailabfrage fehlschlägt oder der Aufruf schlägt dauerhaft aufgrund technischer Probleme fehl).
Schnell wurde klar, dass der geschilderte Fehler (in der Verarbeitung) nicht der einzige mögliche Fehler ist, vielmehr existieren eine ganze Reihe von Fehlerquellen:
Fehler beim Senden von Event-Nachrichten
Fehler bei der empfängerseitigen Verarbeitung von Event-Nachrichten
Fehler beim Übermitteln von Event-Nachrichten (durch interne Fehler der Messaging-Infrastruktur)
Fehler beim Senden von Event-Nachrichten
Fangen wir vorne an, bereits beim Senden kann schon so einiges schiefgehen - das wurde ja bereits ausführlich diskutiert (Transactional Outbox, Dual-Write).
Funktioniert das Senden nicht aufgrund eines Fehlers beim Versand - sagen wir das Messaging-System ist nicht erreichbar (siehe: Fallacities of Distributed Computing), dann sah das Konzept vor, dass die Nachrichten entsprechend gespeichert werden (oder beim Ausfall der Datenbank erneut gesendet werden).
Es wurde im Projekt sehr schnell klar, dass man die Anzahl der Sendeversuche begrenzen sollte …
Fehler bei der empfängerseitigen Verarbeitung von Event-Nachrichten
Fehler beim Empfang von Nachrichten können bspw. durch Fehlschlagen der Validierung einer empfangenen Nachricht oder durch mangelnde Berechtigung des Senders entstehen. Gleichgültig, wie oft man die Nachricht versucht zuzustellen, das Verhalten des Empfängers wird immer das Gleiche sein.
Das übliche Vorgehen ist in diesem Fall die Nachricht zurückzuweisen (durch Exception odee expliziten Rollback der Transacted Session) und die fehlerhafte Nachricht in eine Dead-Letter-Queue (DLQ) einzustellen und diese betriebsseitig entsprechend überwachen.
Eine ganz andere Klasse von Fehlern sind die Fehler, die auftreten können, wenn der Empfänger eine syntaktisch korrekte Nachricht von einem berechtigten Empfänger nicht verarbeitet werden kann. Dieses kann bspw. durch fehlerhafte Angaben innerhalb der Nachricht (bspw. angegebener Kunde existiert nicht) oder auch durch unerlaubte Statusübergänge auf der Empfängerseite auftreten. Bei dieser Fehlerkategorie wäre es fatal, den korrekten Empfang einer Event-Nachricht der Message-oriented Middleware erst dann zu quittieren, wenn die entsprechende Verarbeitung stattgefunden hat - man halte sich nur das Produktionssystem und die Dauer der Produktion vor Augen.
Was nun?
Wir haben uns die Frage gestellt, ob das Zurückweisen und Speichern einer Event-Nachricht in einer Dead-Letter-Queue (DLQ) wirklich ein brillianter Einfall in diesem Anwendungsfall sei. Aus gemachten Erfahrungen mit der Suche nach dem Verbleib von Nachrichten war die eindeutige Antwort: Nein.
Zum Einen hat sich der Sender gefragt, was eigentlich mit seiner Event-Nachricht geschehen ist, eine Fortschrittsmeldung im Sinne eines Folge-Events zur Business-Transaktion hat er ja nie erhalten; zum Anderen ist die Event-Nachricht ja auch auf der Empfängerseite nicht wirklich angekommen, sie wurde ja abgewiesen. Bleibt also nur noch die DLQ mit den entsprechenden Möglichkeiten in ihr zu “recherchieren“.
Weiterhin läuft man Gefahr, dass die DLQ in Nachrichten einfach “absäuft”. Summa summarum, ein nicht wirklich überzeugender Ansatz.
Aus o.g. Gründen fiel die Entscheidung, dass eine Event-Nachricht auch im Falle der Schema-Invalidität oder mangelnder Berechtigung entgegengenommen und gespeichert, jedoch nicht weiterverarbeitet wird (MSG_RECEIVED_NOK).
Es erscheint zielführender, entsprechende Error-Events (welche strenggenommen keine Events sind) in solchen Fehlerfällen zu senden. Dieses passt dann auch zu sonstigen Verarbeitungsfehlern interner Natur wie oben geschildert. So ist auch sichergestellt, dass der Sender auch im Fehlerfall eine entsprechende Benachrichtigung erhält und ggf. darauf reagieren kann.
Ursprüngliche Nachricht:
{
"messageHeader" : {
"TRANSACTION_ID" : "19d2f155-9fa0-4707-bc71-307c2665163f",
"CREATED_WHEN" : "2024-05-19T15:33:12.502379",
"EVENT_CLASSIFICATION" : "ORDERING_ORDER_RECEIVED",
"REPLY_TO" : "Some JMS destination or URI",
"MESSAGE_DATATYPE" : "JSON",
"MESSAGE_SCHEMA_URI" : "Some URI to a schema",
"SENDER_IDENTIFIER" : "ORDERING",
"EVENT_ID" : "97a0d238-6e55-42d0-b978-868ff409650f",
},
"messageBody" : {
"ProductQuantity" : 99,
"DomainId" : "25",
"DomainClass" : "de.brockhaus.order.domain.entity.product.CustomerOrder",
"ProductId" : "PRODUCT_456",
"CustomerId" : "CUSTOMERID_123"
}
}
Fehlernachricht (interner Verarbeitungsfehler):
{
"messageHeader" : {
"TRANSACTION_ID" : "19d2f155-9fa0-4707-bc71-307c2665163f",
"CREATED_WHEN" : "2024-05-19T15:33:12.502379",
"EVENT_CLASSIFICATION" : "CUSTOMERVERIFICATION_ERROR_VALIDATION",
"REPLY_TO" : "Some JMS destination or URI",
"MESSAGE_DATATYPE" : "JSON",
"MESSAGE_SCHEMA_URI" : "Some URI to a schema",
"SENDER_IDENTIFIER" : "CUSTOMERVERIFICATION",
"EVENT_ID" : "97a0d238-6e55-42d0-b978-868ff409650f",
},
"messageBody" : {
"DomainId" : "25",
"DomainClass" : "de.brockhaus.order.domain.entity.product.CustomerOrder",
"ProductId" : "PRODUCT_456",
"CustomerId" : "CUSTOMERID_123"
"ErrorMsg" : "Query for CustomerId provides no result"
}
}
Fehlernachricht (Validierung):
{
"messageHeader" : {
"TRANSACTION_ID" : "19d2f155-9fa0-4707-bc71-307c2665163f",
"CREATED_WHEN" : "2024-05-19T15:33:12.502379",
"EVENT_CLASSIFICATION" : "CUSTOMERVERIFICATION_ERROR_VALIDATION",
"REPLY_TO" : "Some JMS destination or URI",
"MESSAGE_DATATYPE" : "JSON",
"MESSAGE_SCHEMA_URI" : "Some URI to a schema",
"SENDER_IDENTIFIER" : "CUSTOMERVERIFICATION",
"EVENT_ID" : "97a0d238-6e55-42d0-b978-868ff409650f",
},
"messageBody" : {
"DomainId" : "25",
"DomainClass" : "de.brockhaus.order.domain.entity.product.CustomerOrder",
"ProductId" : "PRODUCT_456",
"CustomerId" : "CUSTOMERID_123"
"ErrorMsg" : "Validation of message failed"
}
}
Angemerkt sei: Wir haben uns dahingehend entschieden, diese Nachrichten über ein eigenes Topic zu versenden.
Sagas und Workflows, Workflows und Sagas
Werfen wir noch einmal einen Blick auf das (vereinfachte) Gesamtsystem:
Alles beginnt mit dem Eingang einer Produktbestellung (und dem entsprechenden Event). Auf diesen Event muss eigentlich nur das Verification-System (synonym Service) reagieren in dem Kunde und zu der Bestellung gehörige Dokumente geprüft werden; kann funktionieren, muss aber nicht. Auf jeden Fall gibt einen Event. Im Fehlerfall oder bei Ablehnung der Verifikation reagiert das Bestellsystem; bei positiver Verifikation das Produktionsystem … und so weiter.
Kompensation?
In einem der vorhergehenden Artikel wurde das Thema “Saga“ angeschnitten, zur Erinnerung:
Das Entwurfsmuster Saga ist eine Möglichkeit zum Verwalten der Datenkonsistenz für Microservices in Szenarien mit verteilten Transaktionen. Das Saga-Muster umfasst eine Sequenz von Transaktionen, die alle Dienste aktualisieren und eine Meldung oder ein Ereignis veröffentlichen, um den nächsten Transaktionsschritt auszulösen. Wenn ein Schritt fehlschlägt, werden kompensierende Transaktionen ausgeführt, die den vorhergehenden Transaktionen entgegenwirken.
—microsoft
Streng genommen haben wir eine Saga mit sequentiellen Ablauf und die Folgeschritte werden nur eingeleitet, wenn der vorherige Schritt erfolgreich abgearbeitet wurde:
Der obige Prozess als Teilausschnitt zeigt die rein sequentielle Abarbeitung exemplarisch. Die anderen Schritte unterscheiden sich nicht wesentlich.
Weiterhin muss innerhalb des Prozesses keine wie auch immer geartete Kompensation von vorhergehenden Schritten erfolgen und das Saga-Pattern hier ohne die angeführten “Compensating Transactions“ auskommt (da haben wir aber nochmal Glück gehabt).
Die Frage nach orchestrierten oder choreographierten Sagas haben wir und nicht gestellt. Im Sinne eines Minimal-Viable-Products fiel die Entscheidung, es bei cheoreographierten Sagas zu belassen; eine Entscheidung, die ja nicht für alle Ewigkeiten Bestand haben muss.
Was sonst noch so aus dem Ruder laufen kann
Keine Kompensation nötig, wie wunderbar. Ist jetzt alles so, wie gewünscht? Mitnichten! Es gab noch ausreichend Dinge, die wir im Design brücksichtigen mussten. Da war die Fragestellung nach externen “Störungen“ wie geänderten oder stornierten Bestellungen sowie die Frage danach, wie lange so ein Geschäftsprozess eigentlich laufen soll.
Hinsichtlich der Fragestellung nach externen Störungen kamn wir überein, dass ein jeder Service vor dem Abschluss seiner durch einen Event angestossenen Abarbeitung prüft, ob ein ggf. konkurrierender Event eingetroffen ist und den Status des Aggregats / des Domänenobjekt entsprechend geändert hat.
Hinsichtlich der Überwachung des Geschäftsprozesses und der Gewinnung von weiteren Informationen aus dem Geschäftsprozess, diesem Thema widmet sich der letzte Artikel - “Wohin nur mit den Events” - der Reihe.
Bleiben Sie mir gewogen.