Kapitel 4. Integration von ereignisgesteuerten Architekturen in bestehende Systeme

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Die Umstellung eines Unternehmens auf eine ereignisgesteuerte Architektur erfordert die Integration bestehender Systeme in das Ökosystem. Dein Unternehmen hat vielleicht eine oder mehrere monolithische relationale Datenbankanwendungen. Wahrscheinlich gibt es Punkt-zu-Punkt-Verbindungen zwischen verschiedenen Implementierungen. Vielleicht gibt es bereits ereignisähnliche Mechanismen für die Übertragung von Massendaten zwischen den Systemen, wie z. B. die regelmäßige Synchronisierung von Datenbank-Dumps über einen zwischengeschalteten Dateispeicherort. Wenn du eine ereignisgesteuerte Microservice-Architektur von Grund auf aufbaust und keine Altsysteme hast, ist das großartig! Dann kannst du diesen Abschnitt überspringen (obwohl du vielleicht in Betracht ziehen solltest, dass EDM nicht das Richtige für dein neues Projekt ist). Wenn du jedoch bestehende Altsysteme hast, die unterstützt werden müssen, lies weiter.

In jeder Geschäftsdomäne gibt es Entitäten und Ereignisse, die in der Regel in mehreren Subdomänen benötigt werden. Ein E-Commerce-Händler muss zum Beispiel Produktinformationen, Preise, Lagerbestände und Bilder an verschiedene Kontexte weitergeben. Vielleicht werden die Zahlungen von einem System erfasst, müssen aber in einem anderen validiert werden, und die Analyse des Kaufverhaltens erfolgt in einem dritten System. Wenn diese Daten an einem zentralen Ort als neue Single Source of Truth zur Verfügung stehen, kann jedes System die Daten nutzen, sobald sie verfügbar sind. Die Umstellung auf ereignisgesteuerte Microservices erfordert, dass die erforderlichen Geschäftsdaten im Event Broker als Ereignisströme zur Verfügung gestellt werden. Dies ist ein Prozess, der als Datenbefreiung bekannt ist und bei dem die Daten aus den bestehenden Systemen und Statusspeichern, die sie enthalten, ausgelagert werden.

Auf Daten, die in einem Ereignisstrom erzeugt werden, kann jedes System zugreifen, ob ereignisgesteuert oder nicht. Während ereignisgesteuerte Anwendungen Streaming-Frameworks und systemeigene Verbraucher nutzen können, um die Ereignisse zu lesen, können ältere Anwendungen aufgrund verschiedener Faktoren wie Technologie- und Leistungsbeschränkungen nicht so einfach auf sie zugreifen. In diesem Fall musst du die Ereignisse aus einem Ereignisstrom in einen bestehenden Zustandsspeicher einspeisen.

Es gibt eine Reihe von Mustern und Frameworks für das Sourcing und Sinking von Ereignisdaten. Für jede Technik wird in diesem Kapitel erläutert, warum sie notwendig ist, wie man sie anwendet und welche Kompromisse mit den verschiedenen Ansätzen verbunden sind. Außerdem wird erläutert, wie sich die Datenfreigabe und -senkung in das gesamte Unternehmen einfügen, welche Auswirkungen sie haben und wie du deine Bemühungen erfolgreich gestalten kannst.

Was ist Data Liberation?

Die Datenfreigabe ist die Identifizierung und Veröffentlichung domänenübergreifender Datensätze in ihren entsprechenden Ereignisströmen und ist Teil einer Migrationsstrategie für ereignisgesteuerte Architekturen. Zu den domänenübergreifenden Datensätzen gehören alle Daten, die in einem Datenspeicher gespeichert sind und von anderen externen Systemen benötigt werden. Punkt-zu-Punkt-Abhängigkeiten zwischen bestehenden Diensten und Datenspeichern machen oft die domänenübergreifenden Daten deutlich, die freigegeben werden sollten, wie in Abbildung 4-1 gezeigt, wo drei abhängige Dienste das Altsystem direkt abfragen.

Point to point dependencies, accessing data directly from the underlying service
Abbildung 4-1. Punkt-zu-Punkt-Abhängigkeiten, die direkt auf Daten des zugrunde liegendenDienstes zugreifen

Die Datenbefreiung setzt zwei Hauptmerkmale der ereignisgesteuerten Architektur durch: die einzige Quelle der Wahrheit und die Beseitigung der direkten Kopplung zwischen Systemen. Die befreiten Ereignisströme ermöglichen den Aufbau neuer ereignisgesteuerter Microservices als Konsumenten, wobei bestehende Systeme rechtzeitig migriert werden. Reaktive ereignisgesteuerte Frameworks und Dienste können nun zum Konsumieren und Verarbeiten von Daten verwendet werden, und nachgelagerte Konsumenten müssen nicht mehr direkt mit dem Quelldatensystem gekoppelt werden.

Indem sie als eine einzige Quelle der Wahrheit dienen, standardisieren diese Streams auch die Art und Weise, wie Systeme im gesamten Unternehmen auf Daten zugreifen. Die Systeme müssen nicht mehr direkt mit den zugrundeliegenden Datenspeichern und Anwendungen verbunden werden, sondern können nur noch auf die Datenverträge der Ereignisströme zugreifen. Der Arbeitsablauf nach der Liberalisierung ist in Abbildung 4-2 dargestellt.

Post-data-liberation workflow
Abbildung 4-2. Arbeitsablauf nach der Datenfreigabe

Kompromisse für die Datenbefreiung

Ein Datensatz und sein befreiter Ereignisstrom müssen vollständig synchron gehalten werden, auch wenn sich diese Anforderung aufgrund der Latenzzeit bei der Ereignisausbreitung auf eine eventuelle Konsistenz beschränkt. Ein Strom befreiter Ereignisse muss sich in ein exaktes Replikat der Quelltabelle zurückverwandeln. Diese Eigenschaft wird bei ereignisgesteuerten Microservices ausgiebig genutzt (wie in Kapitel 7 beschrieben). Im Gegensatz dazu bauen Legacy-Systeme ihre Datensätze nicht aus den Ereignisströmen wieder auf, sondern verfügen in der Regel über eigene Sicherungs- und Wiederherstellungsmechanismen und lesen absolut nichts aus dem befreiten Ereignisstrom zurück.

In der perfekten Welt würden alle Zustände von der einzigen Quelle der Wahrheit der Ereignisströme erstellt, verwaltet, gepflegt und wiederhergestellt werden. Alle gemeinsam genutzten Zustände sollten zuerst im Event Broker veröffentlicht werden und dann an alle Dienste zurückgegeben werden, die den Zustand benötigen, einschließlich des Dienstes, der die Daten überhaupt erst erzeugt hat (siehe Abbildung 4-3).

Publish to stream before materializing
Abbildung 4-3. Vor dem Materialisieren im Stream veröffentlichen

Während das Ideal, den Status im Event Broker zu halten, für neue Microservices und überarbeitete Legacy-Anwendungen möglich ist, ist es nicht unbedingt für alle Anwendungen verfügbar oder praktisch. Dies gilt insbesondere für Dienste, die wahrscheinlich nie über die anfängliche Integration mit Mechanismen zur Erfassung von Änderungsdaten hinaus überarbeitet oder geändert werden. Legacy-Systeme können sowohl extrem wichtig für das Unternehmen sein als auch unerschwinglich schwer zu refaktorisieren, wobei die schlimmsten Übeltäter als große Matschkugel angesehen werden. Trotz der Komplexität eines Systems müssen andere neue Systeme auf seine internen Daten zugreifen können. Auch wenn Refactoring absolut wünschenswert ist, gibt es eine Reihe von Problemen, die dies in der Realität verhindern:

Begrenzte Unterstützung für Entwickler

Viele Altsysteme haben nur minimale Entwicklerunterstützung und erfordern Lösungen mit geringem Aufwand, um freie Daten zu generieren.

Aufwand für Refactoring

Die Überarbeitung der bestehenden Anwendungsworkflows in eine Mischung aus asynchroner ereignisgesteuerter und synchroner MVC (Model-View-Controller)-Webanwendungslogik kann unerschwinglich teuer sein, insbesondere bei komplexen Legacy-Monolithen.

Risiko der Unterstützung von Altlasten

Änderungen an Altsystemen können unbeabsichtigte Folgen haben, vor allem wenn die Zuständigkeiten des Systems aufgrund von technischen Schulden und nicht identifizierten Punkt-zu-Punkt-Verbindungen mit anderen Systemen unklar sind.

Hier gibt es eine Möglichkeit für einen Kompromiss. Du kannst Datenbefreiungsmuster verwenden, um die Daten aus dem Datenspeicher zu extrahieren und die erforderlichen Ereignisströme zu erzeugen. Dies ist eine Form der unidirektionalen ereignisgesteuerten Architektur, da das Altsystem nicht aus dem befreiten Ereignisstrom zurückliest, wie in Abbildung 4-3 dargestellt. Stattdessen besteht das grundlegende Ziel darin, den internen Datensatz durch eine streng kontrollierte Veröffentlichung von Ereignisdaten mit dem externen Ereignisstrom zu synchronisieren. Der Ereignisstrom wird schließlich mit dem internen Datensatz der Legacy-Anwendung konsistent sein, wie in Abbildung 4-4 dargestellt.

Liberating and materializing state between two services
Abbildung 4-4. Freigabe und Materialisierung von Zuständen zwischen zwei Diensten

Befreite Daten in Ereignisse umwandeln

Für befreite Daten gelten, wie für jedes andere Ereignis auch, die gleichen Empfehlungen zur Schematisierung, die in Kapitel 3 vorgestellt wurden. Eine der Eigenschaften eines gut definierten Ereignisstroms ist, dass es ein explizit definiertes und evolutionär kompatibles Schema für die darin enthaltenen Ereignisse gibt. Du solltest sicherstellen, dass die Verbraucher grundlegende Datenqualitätsgarantien als Teil des durch das Schema definierten Datenvertrags haben. Änderungen am Schema können nur nach evolutionären Regeln vorgenommen werden.

Tipp

Verwende in deinem Unternehmen dasselbe Standardformat für freigestellte Ereignisdaten und native Ereignisdaten.

Definitionsgemäß sind die Daten, die im gesamten Unternehmen am relevantesten sind und verwendet werden, auch die Daten, die am dringendsten freigegeben werden müssen. Änderungen an den Datendefinitionen der Quelle, wie z. B. das Anlegen neuer Felder, das Ändern bestehender Felder oder das Löschen anderer Felder, können dazu führen, dass dynamisch veränderte Daten an die nachgelagerten Verbraucher weitergegeben werden. Fehlschlägt die Verwendung eines explizit definierten Schemas für freigegebene Daten, sind die nachgelagerten Verbraucher gezwungen, etwaige Inkompatibilitäten zu beheben. Dies ist äußerst problematisch für die Bereitstellung einer einzigen Wahrheitsquelle, da die nachgelagerten Verbraucher nicht versuchen sollten, die Daten selbst zu analysieren oder zu interpretieren. Es ist äußerst wichtig, ein zuverlässiges und aktuelles Schema der erzeugten Daten bereitzustellen und die Entwicklung der Daten im Laufe der Zeit sorgfältig zu berücksichtigen.

Muster für die Datenbefreiung

Es gibt drei Hauptmuster für die Datenfreigabe, die du verwenden kannst, um Daten aus dem zugrunde liegenden Datenspeicher zu extrahieren. Da die befreiten Daten die neue einzige Quelle der Wahrheit bilden sollen, müssen sie alle Daten aus dem Datenspeicher enthalten. Außerdem müssen diese Daten durch neue Einfügungen, Aktualisierungen und Löschungen auf dem neuesten Stand gehalten werden.

Abfragebasiert

Du extrahierst Daten, indem du den zugrunde liegenden State Store abfragst. Dies kann für jeden Datenspeicher durchgeführt werden.

Log-basiert

Du extrahierst Daten, indem du die Änderungen an den zugrundeliegenden Datenstrukturen im Nur-Anhang-Protokoll verfolgst. Diese Option ist nur für ausgewählte Datenspeicher verfügbar, die ein Protokoll über die an den Daten vorgenommenen Änderungen führen.

Tabellenbasiert

Bei diesem Muster schiebst du zunächst Daten in eine Tabelle, die als Ausgabewarteschlange verwendet wird. Ein anderer Thread oder separater Prozess fragt die Tabelle ab, gibt die Daten an den entsprechenden Ereignisstrom weiter und löscht dann die zugehörigen Einträge. Für diese Methode muss der Datenspeicher sowohl Transaktionen als auch einen Warteschlangenmechanismus unterstützen, in der Regel eine eigenständige Tabelle, die für die Verwendung als Warteschlange konfiguriert ist.

Obwohl jedes Muster einzigartig ist, gibt es eine Gemeinsamkeit zwischen den drei Mustern. Jedes Muster sollte seine Ereignisse in sortierter Zeitstempel-Reihenfolge produzieren, indem es die jüngste updated_at Zeit des Quelldatensatzes in der Kopfzeile des Ausgabe-Ereignisdatensatzes verwendet. Auf diese Weise wird ein Ereignisstrom erzeugt, der mit einem Zeitstempel versehen ist, der sich nach dem Auftreten des Ereignisses richtet und nicht nach der Zeit, zu der der Produzent das Ereignis veröffentlicht hat. Dies ist besonders wichtig für die Datenfreigabe, da es genau wiedergibt, wann die Ereignisse im Workflow tatsächlich eingetreten sind. Die zeitstempelbasierte Verschachtelung von Ereignissen wird in Kapitel 6 näher erläutert.

Rahmenwerke zur Datenbefreiung

Eine Methode zur Datenfreigabe ist die Verwendung eines speziellen, zentralisierten Frameworks zur Extraktion von Daten in Ereignisströmen. Beispiele für zentralisierte Frameworks zur Erfassung von Ereignisströmen sind Kafka Connect (exklusiv für die Kafka-Plattform), Apache Gobblin und Apache NiFi. Jedes Framework ermöglicht es dir, eine Abfrage auf den zugrunde liegenden Datensatz auszuführen und die Ergebnisse in deine Ausgangs-Event-Streams zu leiten. Jede Option ist skalierbar, so dass du weitere Instanzen hinzufügen kannst, um die Kapazität für die Ausführung von CDC-Aufträgen (Change Data Capture) zu erhöhen. Sie unterstützen verschiedene Stufen der Integration mit der von Confluent angebotenen Schemaregistrierung (Apache Kafka), können aber natürlich auch so angepasst werden, dass sie andere Schemaregistrierungen unterstützen. Weitere Informationen findest du unter "Schema-Registry".

Nicht alle Prozesse zur Datenfreigabe erfordern ein spezielles Framework, und viele Systeme sind besser geeignet, um ihre eigene Datenproduktion im Ereignisstrom selbst in die Hand zu nehmen. Tatsächlich fördern diese Frameworks ungewollt Anti-Patterns beim Datenzugriff. Eines der häufigsten Gegenmuster ist die Offenlegung interner Datenmodelle für externe Systeme, was die Kopplung weiter erhöht, anstatt sie zu verringern, was einer der größten Vorteile ereignisgesteuerter Architekturen ist. Darauf werden wir im weiteren Verlauf des Kapitels eingehen.

Daten durch Abfrage befreien

Bei der abfragebasierten Datenfreigabe wird der Datenspeicher abgefragt und die ausgewählten Ergebnisse werden an einen zugehörigen Ereignisstrom gesendet. Ein Client wird verwendet, um einen bestimmten Datensatz aus dem Datenspeicher über die entsprechende API, SQL oder eine SQL-ähnliche Sprache abzufragen. Ein Datensatz muss in großen Mengen abgefragt werden, um die Historie der Ereignisse zu erhalten. Es folgen regelmäßige Aktualisierungen, die sicherstellen, dass Änderungen in den ausgegebenen Ereignisstrom einfließen.

Es gibt mehrere Arten von Abfragen, die in diesem Muster verwendet werden.

Bulk Loading

Beim Bulk Loading werden alle Daten aus dem Datensatz abgefragt und geladen. Bulk-Loads werden durchgeführt, wenn die gesamte Tabelle in jedem Abfrageintervall geladen werden muss, sowie vor laufenden inkrementellen Aktualisierungen.

Das Bulk Loading kann teuer sein, da der gesamte Datensatz aus dem Datenspeicher geholt werden muss. Bei kleineren Datensätzen ist das in der Regel kein Problem, aber bei großen Datensätzen, insbesondere bei Millionen oder Milliarden von Datensätzen, kann es schwierig sein, sie zu erhalten. Für die Abfrage und Verarbeitung sehr großer Datensätze empfehle ich dir, die bewährten Methoden für deinen jeweiligen Datenspeicher zu recherchieren, da diese je nach Implementierung stark variieren können.

Inkrementelles Laden von Zeitstempeln

Beim inkrementellen Laden mit Zeitstempel werden alle Daten seit dem höchsten Zeitstempel der vorherigen Abfrageergebnisse abgefragt und geladen. Bei diesem Ansatz wird eine updated-at Spalte oder ein Feld im Datensatz verwendet, das die Zeit festhält, zu der der Datensatz zuletzt geändert wurde. Bei jeder inkrementellen Aktualisierung werden nur Datensätze mit updated-at Zeitstempeln abgefragt, die später als der letzte verarbeitete Zeitpunkt liegen.

Autoincrementing ID Laden

Beim autoinkrementierenden ID-Laden werden alle Daten abgefragt und geladen, die größer sind als der letzte Wert der ID. Dies erfordert ein streng geordnetes autoinkrementierendes Integer oder Long Feld. Bei jeder inkrementellen Aktualisierung werden nur Datensätze mit einer ID abgefragt, die größer ist als die zuletzt verarbeitete ID. Dieser Ansatz wird häufig für die Abfrage von Tabellen mit unveränderlichen Datensätzen verwendet, z. B. bei der Verwendung der Outbox-Tabellen (siehe "Befreiung von Daten mithilfe von Change-Data-Capture-Protokollen").

Benutzerdefinierte Abfragen

Eine benutzerdefinierte Abfrage wird nur durch die Abfragesprache des Clients begrenzt. Dieser Ansatz wird häufig verwendet, wenn der Kunde nur eine bestimmte Teilmenge von Daten aus einem größeren Datensatz benötigt oder wenn er Daten aus mehreren Tabellen zusammenführt und denormalisiert, um das interne Datenmodell nicht zu stark zu belasten. Zum Beispiel könnte ein Nutzer die Daten von Geschäftspartnern nach einem bestimmten Feld filtern, wobei die Daten jedes Partners an seinen eigenen Ereignisstrom gesendet werden.

Inkrementelle Aktualisierung

Der erste Schritt bei jeder inkrementellen Aktualisierung besteht darin, sicherzustellen, dass die Datensätze deines Datensatzes den erforderlichen Zeitstempel oder die automatische ID haben. Es muss ein Feld geben, mit dem die Abfrage die bereits verarbeiteten Datensätze von den noch zu verarbeitenden herausfiltern kann. Datensätze, in denen diese Felder fehlen, müssen hinzugefügt werden, und der Datenspeicher muss so konfiguriert werden, dass er den erforderlichen updated_atZeitstempel oder das Feld für die Autoinkrementierung-ID ausfüllt. Wenn die Felder dem Datensatz nicht hinzugefügt werden können, sind inkrementelle Aktualisierungen mit einem abfragebasierten Muster nicht möglich.

Der zweite Schritt besteht darin, die Häufigkeit der Abfrage und die Latenzzeit der Aktualisierungen zu bestimmen. Eine höhere Aktualisierungsfrequenz sorgt für eine geringere Latenz bei den nachgelagerten Datenaktualisierungen, allerdings auf Kosten einer höheren Gesamtlast für den Datenspeicher. Es ist auch wichtig zu überlegen, ob der Abstand zwischen den Abfragen ausreicht, um alle Daten zu laden. Wenn du eine neue Abfrage startest, während die alte noch geladen wird, kann es zu Wettlaufsituationen kommen, bei denen ältere Daten neuere Daten in den Ausgabe-Event-Streams überschreiben.

Nachdem das Feld für die inkrementelle Aktualisierung ausgewählt und die Häufigkeit der Aktualisierungen festgelegt wurde, wird als letzter Schritt ein einmaliger Bulkload durchgeführt, bevor die inkrementelle Aktualisierung aktiviert wird. Dieser Bulkload muss alle vorhandenen Daten im Datensatz abfragen und produzieren, bevor weitere inkrementelle Aktualisierungen vorgenommen werden können.

Vorteile der abfragebasierten Aktualisierung

Die abfragebasierte Aktualisierung hat eine Reihe von Vorteilen, unter anderem:

Anpassungsfähigkeit

Jeder Datenspeicher kann abgefragt werden, und die gesamte Palette der Client-Optionen für die Abfrage ist verfügbar.

Unabhängige Wahlperioden

Bestimmte Abfragen können häufiger ausgeführt werden, um strengere SLAs (Service-Level-Agreements) einzuhalten, während andere, teurere Abfragen seltener ausgeführt werden können, um Ressourcen zu sparen.

Isolierung der internen Datenmodelle

Relationale Datenbanken können durch die Verwendung von Ansichten oder materialisierten Ansichten der zugrunde liegenden Daten eine Isolierung vom internen Datenmodell bieten. Mit dieser Technik lassen sich Informationen aus dem Domänenmodell verbergen, die außerhalb des Datenspeichers nicht sichtbar sein sollen.

Vorsicht

Erinnere dich daran, dass die freigegebenen Daten die einzige Quelle der Wahrheit sein werden. Überlege, ob verborgene oder ausgelassene Daten stattdessen freigegeben werden sollten oder ob das Quelldatenmodell überarbeitet werden muss. Dies geschieht häufig bei der Datenfreigabe von Altsystemen, bei denen Geschäftsdaten und Entitätsdaten im Laufe der Zeit miteinander verwoben wurden.

Nachteile der abfragebasierten Aktualisierung

Die abfragebasierte Aktualisierung hat aber auch einige Nachteile:

Erforderlich updated-at Zeitstempel

Die zugrundeliegende Tabelle oder der Namensraum der abzufragenden Ereignisse muss eine Spalte mit ihrem updated-at Zeitstempel haben. Dies ist wichtig, um die letzte Aktualisierungszeit der Daten zu verfolgen und inkrementelle Aktualisierungen vorzunehmen.

Nicht zurückverfolgbare harte Löschungen

Harte Löschungen werden in den Abfrageergebnissen nicht angezeigt, daher beschränkt sich die Verfolgung von Löschungen auf flaggenbasierte weiche Löschungen, wie z. B. eine boolesche is_deleted Spalte.

Brüchige Abhängigkeit zwischen Datensatzschema und Ausgabe-Ereignisschema

Es kann zu Schemaänderungen im Datensatz kommen, die mit den Regeln des nachgelagerten Ereignisformats nicht kompatibel sind. Brüche werden immer wahrscheinlicher, wenn der Befreiungsmechanismus von der Codebasis der Datenspeicheranwendung getrennt ist, was bei abfragebasierten Systemen normalerweise der Fall ist.

Intermittierende Erfassung

Da die Daten nur in Abfrageintervallen synchronisiert werden, kann es sein, dass eine Reihe von individuellen Änderungen am selben Datensatz nur als ein einziges Ereignis angezeigt wird.

Ressourcenverbrauch in der Produktion

Abfragen nutzen die zugrundeliegenden Systemressourcen zur Ausführung, was auf einem Produktionssystem zu inakzeptablen Verzögerungen führen kann. Dieses Problem kann durch den Einsatz eines schreibgeschützten Replikats entschärft werden, allerdings entstehen dadurch zusätzliche Kosten und eine höhere Systemkomplexität.

Variable Abfrageleistung aufgrund von Datenänderungen

Die Menge der abgefragten und zurückgegebenen Daten variiert je nach Änderungen an den zugrunde liegenden Daten. Im schlimmsten Fall wird jedes Mal der gesamte Datenbestand geändert. Das kann zu Wettlaufsituationen führen, wenn eine Abfrage nicht beendet ist, bevor die nächste beginnt.

Daten mithilfe von Änderungsdatenerfassungsprotokollen befreien

Ein weiteres Muster für die Befreiung von Daten ist die Verwendung der dem Datenspeicher zugrunde liegenden Change-Data-Capture-Logs(binäre Logs in MySQL, Write-Ahead-Logs für PostgreSQL) als Informationsquelle. Dabei handelt es sich um eine reine Datenprotokollierungsstruktur, in der alles festgehalten wird, was mit den verfolgten Datensätzen im Laufe der Zeit passiert ist. Zu diesen Änderungen gehören das Erstellen, Löschen und Aktualisieren einzelner Datensätze sowie das Erstellen, Löschen und Ändern der einzelnen Datensätze und ihrer Schemata.

Die technologischen Optionen für die Erfassung von Änderungsdaten sind enger gefasst als die für die abfragebasierte Erfassung. Nicht alle Datenspeicher implementieren eine unveränderliche Protokollierung von Änderungen, und von denjenigen, die dies tun, verfügen nicht alle über Standardkonnektoren zum Extrahieren der Daten. Dieser Ansatz eignet sich vor allem für ausgewählte relationale Datenbanken wie MySQL und PostgreSQL, aber auch jeder andere Datenspeicher, der über umfassende Änderungsprotokolle verfügt, ist ein geeigneter Kandidat. Viele andere moderne Datenspeicher bieten Ereignis-APIs, die als Ersatz für ein physisches Write-Ahead-Log dienen. MongoDB bietet zum Beispiel eine Change Streams-Schnittstelle, während Couchbase über sein internes Replikationsprotokoll Zugriff auf die Replikation bietet.

Es ist unwahrscheinlich, dass das Protokoll des Datenspeichers alle Änderungen seit Beginn der Zeit enthält, da es sich um eine riesige Datenmenge handeln kann, die normalerweise nicht aufbewahrt werden muss. Bevor du mit der Erfassung der Änderungsdaten aus dem Protokoll des Datenspeichers beginnst, musst du einen Snapshot der vorhandenen Daten erstellen. Dieser Snapshot beinhaltet in der Regel eine große, leistungsbeeinträchtigende Abfrage der Tabelle und wird allgemein als Bootstrapping bezeichnet. Du musst sicherstellen, dass es Überschneidungen zwischen den Datensätzen in den Ergebnissen der Bootstrapping-Abfrage und den Datensätzen im Protokoll gibt, damit du nicht versehentlich einen Datensatz übersiehst.

Bei der Erfassung von Ereignissen aus den Änderungsprotokollen musst du einen Checkpoint setzen, der je nach verwendetem Tool bereits eingebaut ist. Falls die Erfassung der Änderungsdaten fehlschlägt, wird der Checkpoint verwendet, um den zuletzt gespeicherten Changelog-Index wiederherzustellen. Bei diesem Ansatz können nur einmalige Datensätze erstellt werden, was für die entitätsbasierte Art der Datenfreigabe geeignet ist. Die Erstellung eines zusätzlichen Datensatzes ist unerheblich, da die Aktualisierung von Entitätsdatenidempotent ist.

Es gibt eine Reihe von Optionen, um Daten aus Changelogs zu beziehen. Debezium ist eine der beliebtesten Optionen für relationale Datenbanken, da es die gängigsten Datenbanken unterstützt. Debezium kann mit seinen bestehenden Implementierungen sowohl für Apache Kafka als auch für Apache Pulsar Datensätze erzeugen. Die Unterstützung weiterer Broker ist durchaus möglich, auch wenn dies möglicherweise einige interne Entwicklungsarbeit erfordert. Maxwell ist ein weiteres Beispiel für einen binären Log-Reader, auch wenn er derzeit nur MySQL-Datenbanken unterstützt und nur Daten für Apache Kafka produzieren kann.

The end-to-end workflow of a Debezium capturing data from a MySQL database's binary log and writing it to event streams in Kafka
Abbildung 4-5. Der End-to-End-Workflow eines Debeziums, das Daten aus dem Binärprotokoll einer MySQL-Datenbank erfasst und sie in Ereignisströme in Kafka schreibt

Abbildung 4-5 zeigt eine MySQL-Datenbank, die ihr binäres Changelog ausgibt. Ein Kafka-Connect-Dienst, auf dem ein Debezium-Konnektor läuft, empfängt das binäre Rohprotokoll. Debezium parst die Daten und wandelt sie in einzelne Ereignisse um. Anschließend sendet ein Event-Router jedes Ereignis an einen bestimmten Event-Stream in Kafka, abhängig von der Quelltabelle des Ereignisses. Nachgeschaltete Verbraucher können nun auf die Datenbankinhalte zugreifen, indem sie die entsprechenden Ereignisströme von Kafka abrufen.

Vorteile der Verwendung von Datenspeicherprotokollen

Einige Vorteile der Verwendung von Datenspeicherprotokollen sind:

Tracking löschen

Binäre Logs enthalten harte Löschungen von Datensätzen. Diese können in Löschereignisse umgewandelt werden, ohne dass Soft Deletes wie bei abfragebasierten Aktualisierungen erforderlich sind.

Minimale Auswirkungen auf die Leistung des Datenspeichers

Bei Datenspeichern, die Write-Ahead und binäre Protokolle verwenden, kann die Erfassung von Änderungsdaten ohne Auswirkungen auf die Leistung des Datenspeichers durchgeführt werden. Bei Datenspeichern, die Änderungstabellen verwenden, wie z. B. in SQL Server, hängt die Auswirkung von der Datenmenge ab.

Updates mit niedriger Latenzzeit

Aktualisierungen können weitergegeben werden, sobald das Ereignis in die Binär- und Write-Ahead-Logs geschrieben wird. Dies führt zu einer sehr geringen Latenzzeit im Vergleich zu anderen Datenfreigabemustern.

Nachteile der Verwendung von Datenbankprotokollen

Im Folgenden sind einige der Nachteile der Verwendung von Datenbankprotokollen aufgeführt:

Offenlegung der internen Datenmodelle

Das interne Datenmodell wird in den Changelogs vollständig offengelegt. Die Isolierung des zugrundeliegenden Datenmodells muss sorgfältig und selektiv gehandhabt werden, anders als bei abfragebasierten Aktualisierungen, bei denen Ansichten zur Isolierung verwendet werden können.

Denormalisierung außerhalb des Datenspeichers

Changelogs enthalten nur die Ereignisdaten. Einige CDC-Mechanismen können aus materialisierten Ansichten extrahieren, aber für viele andere muss die Denormalisierung außerhalb des Datenspeichers erfolgen. Dies kann dazu führen, dass hochgradig normalisierte Ereignisströme erzeugt werden, die nachgelagerte Microservices erfordern, um Fremdschlüssel-Joins und Denormalisierung zu handhaben.

Brüchige Abhängigkeit zwischen Datensatzschema und Ausgabe-Ereignisschema

Ähnlich wie der abfragebasierte Datenbefreiungsprozess existiert der binärprotokollbasierte Prozess außerhalb der Datenspeicheranwendung. Gültige Änderungen im Datenspeicher, wie z. B. das Ändern eines Datensatzes oder das Neudefinieren eines Feldtyps, können mit den spezifischen Evolutionsregeln des Ereignisschemas völlig unvereinbar sein.

Daten mit Outbox-Tabellen befreien

Eine Outbox-Tabelle enthält bemerkenswerte Änderungen an den internen Daten eines Datenspeichers, wobei jede wichtige Aktualisierung als eigene Zeile gespeichert wird. Bei jeder Einfügung, Aktualisierung oder Löschung einer der für die Änderungsdatenerfassung markierten Datenspeichertabellen kann ein entsprechender Datensatz in der Outbox-Tabelle veröffentlicht werden. Jede Tabelle, für die Änderungsdaten erfasst werden, kann eine eigene Postausgangstabelle haben, oder es kann ein einziger Postausgang für alle Änderungen verwendet werden (mehr dazu in Kürze).

Sowohl die internen Tabellenaktualisierungen als auch die Aktualisierungen des Postausgangs müssen in einer einzigen Transaktion gebündelt werden, so dass beide nur dann stattfinden, wenn die gesamte Transaktion erfolgreich ist. Gelingt dies nicht, kann es zu Abweichungen vom Ereignisstrom als einziger Quelle der Wahrheit kommen, die schwer zu erkennen und zu beheben sind. Bei diesem Muster handelt es sich um einen invasiveren Ansatz zur Erfassung von Änderungsdaten, da entweder der Datenspeicher oder die Anwendungsschicht geändert werden muss, was die Beteiligung der Datenspeicherentwickler erfordert. Das Outbox-Table-Pattern nutzt die Langlebigkeit des Datenspeichers, um ein vorausschauendes Protokoll für Ereignisse zu erstellen, die in externen Ereignisströmen veröffentlicht werden sollen.

Die Datensätze in Ausgangstabellen müssen eine strenge Ordnungskennung haben, denn derselbe Primärschlüssel kann in kurzer Zeit viele Male aktualisiert werden. Alternativ könntest du die vorherige Aktualisierung des Primärschlüssels überschreiben, aber dazu muss der vorherige Eintrag erst gefunden werden, was zu einem zusätzlichen Leistungsaufwand führt. Außerdem bedeutet dies, dass der überschriebene Datensatz nicht nachgelagert wird.

Eine automatisch aufsteigende ID, die beim Einfügen zugewiesen wird, wird am besten verwendet, um die Reihenfolge zu bestimmen, in der die Ereignisse veröffentlicht werden sollen. Außerdem sollte eine created_at Zeitstempelspalte angelegt werden, die den Zeitpunkt der Erstellung des Datensatzes im Datenspeicher widerspiegelt und bei der Veröffentlichung im Ereignisstrom anstelle der Wanduhrzeit verwendet werden kann. So kann das Zeitplannungsprogramm, wie in Kapitel 6 beschrieben, die Ereignisse genau ineinander verschachteln.

The end-to-end workflow of an outbox table CDC solution
Abbildung 4-6. Der End-to-End-Workflow einer CDC-Lösung für Postausgangstabellen

Abbildung 4-6 zeigt den End-to-End-Workflow. Aktualisierungen der internen Tabellen durch den Datenspeicher-Client werden in einer Transaktion mit einer Aktualisierung der Outbox-Tabelle verpackt, so dass bei Ausfällen die Daten zwischen den beiden konsistent bleiben. In der Zwischenzeit wird ein separater Anwendungsthread oder -prozess verwendet, um die Outboxen kontinuierlich abzufragen und die Daten für die entsprechenden Event-Streams zu produzieren. Sobald die Daten erfolgreich produziert wurden, werden die entsprechenden Datensätze im Postausgang gelöscht. Im Falle eines Ausfalls, sei es des Datenspeichers, des Konsumenten/Produzenten oder des Event Brokers selbst, bleiben die Datensätze im Postausgang erhalten, ohne dass ein Verlust droht. Dieses Mustergarantiert eine mindestens einmalige Zustellung.

Leistungsüberlegungen

Die Einbeziehung von Ausgangstabellen bedeutet eine zusätzliche Belastung für den Datenspeicher und die Anwendungen, die die Anfragen bearbeiten. Bei kleinen Datenspeichern mit minimaler Last kann dieser Mehraufwand völlig unbemerkt bleiben. Bei sehr großen Datenspeichern, vor allem bei solchen mit hoher Auslastung und vielen zu erfassenden Tabellen, kann dies jedoch sehr teuer werden. Die Kosten dieses Ansatzes sollten von Fall zu Fall bewertet und gegen die Kosten einer reaktiven Strategie wie dem Parsen der Änderungsdatenerfassungsprotokolle abgewogen werden.

Interne Datenmodelle isolieren

Ein Postausgang muss nicht 1:1 mit einer internen Tabelle übereinstimmen. Einer der größten Vorteile der Outbox ist, dass der Datenspeicher-Client das interne Datenmodell von den nachgelagerten Verbrauchern isolieren kann. Das interne Datenmodell der Domäne kann eine Reihe von hochgradig normalisierten Tabellen enthalten, die für relationale Operationen optimiert sind, aber für die Verwendung durch nachgelagerte Verbraucher weitgehend ungeeignet sind. Selbst einfache Domänen können aus mehreren Tabellen bestehen, die, wenn sie als unabhängige Datenströme offengelegt würden, für die Nutzung durch nachgelagerte Verbraucher rekonstruiert werden müssten. Das wird schnell extrem teuer, da mehrere nachgelagerte Teams das Domänenmodell rekonstruieren und mit relationalen Daten in Ereignisströmen umgehen müssen.

Warnung

Die Offenlegung des internen Datenmodells für nachgelagerte Verbraucher ist ein unzulässiges Verhaltensmuster. Nachgelagerte Verbraucher sollten nur auf Daten zugreifen, die mit öffentlich zugänglichen Datenverträgen formatiert sind, wie in Kapitel 3 beschrieben.

Der Datenspeicher-Client kann stattdessen die Daten beim Einfügen denormalisieren, so dass der Postausgang den beabsichtigten öffentlichen Datenvertrag widerspiegelt, was allerdings auf Kosten der Leistung und des Speicherplatzes geht. Eine andere Möglichkeit ist die Beibehaltung der 1:1-Zuordnung von Änderungen zu den Ausgangs-Ereignisströmen und die Denormalisierung der Ströme mit einem nachgeschalteten Ereignisprozessor, der nur für diese Aufgabe zuständig ist. Diesen Prozess nenne ich Eventifizierung, da er hochgradig normalisierte relationale Daten in einfach zu konsumierende Einzelereignis-Updates umwandelt. Damit wird das nachgeahmt, was der Datenspeicher-Client tun könnte, allerdings außerhalb des Datenspeichers, um die Last zu verringern. Ein Beispiel hierfür ist in Abbildung 4-7 zu sehen, in der ein Benutzer anhand von Benutzer, Standort und Arbeitgeber denormalisiert wird.

Eventification of public User events using private User, Location, and Employer event streams
Abbildung 4-7. Eventifizierung von öffentlichen Nutzerereignissen mit privaten Nutzer-, Standort- und Arbeitgeber-Ereignisströmen

In diesem Beispiel hat der Nutzer einen Verweis auf die Stadt, das Bundesland und das Land, in dem er lebt, sowie einen Verweis auf seinen aktuellen Arbeitgeber. Es ist verständlich, dass ein nachgeschalteter Verbraucher eines Benutzerereignisses einfach alles über jeden Benutzer in einem einzigen Ereignis haben möchte, anstatt gezwungen zu sein, jeden Datenstrom in einem Zustandsspeicher zu materialisieren und mit relationalen Werkzeugen zu denormalisieren. Die unbearbeiteten, normalisierten Ereignisse werden von den Postausgängen in eigene Ereignisströme umgewandelt, die jedoch in einem privaten Namensraum vor dem Rest des Unternehmens geschützt werden (siehe "Tagging von Ereignisstrom-Metadaten"), um das interne Datenmodell zu schützen.

Die Eventifizierung des Benutzers erfolgt durch die Denormalisierung der Entität "Benutzer" und das Auflösen aller internen Datenstrukturen. Für diesen Prozess müssen materialisierte Tabellen für Benutzer, Standort und Arbeitgeber gepflegt werden, so dass bei Aktualisierungen die Verknüpfungslogik erneut ausgeführt und Aktualisierungen für alle betroffenen Benutzer ausgegeben werden können. Das endgültige Ereignis wird an den öffentlichen Namensraum der Organisation gesendet, damit alle nachgelagerten Verbraucher es nutzen können.

Das Ausmaß, in dem die internen Datenmodelle von den externen Verbrauchern isoliert sind, wird in Unternehmen, die sich auf ereignisgesteuerte Microservices umstellen, oft zu einem Streitpunkt. Die Isolierung des internen Datenmodells ist wichtig, um die Entkopplung und Unabhängigkeit von Diensten zu gewährleisten und sicherzustellen, dass die Systeme nur aufgrund neuer Geschäftsanforderungen geändert werden müssen, nicht aber aufgrund von vorgelagerten Änderungen des internen Datenmodells.

Sicherstellung der Schema-Kompatibilität

Die Schemaserialisierung (und damit die Validierung) kann auch in den Erfassungsworkflow integriert werden. Dies kann entweder vor oder nach dem Schreiben des Ereignisses in die Postausgangstabelle erfolgen. Ein Erfolg bedeutet, dass das Ereignis im Arbeitsablauf fortgesetzt werden kann, während ein Misserfolg einen manuellen Eingriff erfordert, um die Ursache zu ermitteln und Datenverlust zu vermeiden.

Die Serialisierung vor dem Commit der Transaktion in die Outbox-Tabelle bietet die beste Garantie für die Datenkonsistenz. Wenn die Serialisierung fehlschlägt, wird die Transaktion fehlgeschlagen und alle Änderungen an den internen Tabellen werden rückgängig gemacht, so dass die Outbox-Tabelle und die internen Tabellen synchron bleiben. Dieser Prozess ist in Abbildung 4-8 dargestellt. Bei einer erfolgreichen Validierung wird das Ereignis serialisiert und ist bereit für die Veröffentlichung im Ereignisstrom. Der Hauptvorteil dieses Ansatzes besteht darin, dass Dateninkonsistenzen zwischen dem internen Status und dem ausgegebenen Ereignisstrom deutlich reduziert werden. Die Daten des Ereignisstroms werden als Bürger erster Klasse behandelt, und die Veröffentlichung korrekter Daten wird als ebenso wichtig angesehen wie die Aufrechterhaltung eines konsistenten internen Zustands.

Serializing change-data before writing to outbox table
Abbildung 4-8. Serialisierung der Änderungsdaten vor dem Schreiben in die Outbox-Tabelle

Die Serialisierung vor dem Schreiben in den Postausgang bietet dir auch die Möglichkeit, einen einzigen Postausgang für alle Transaktionen zu verwenden. Das Format ist einfach, da der Inhalt hauptsächlich aus serialisierten Daten besteht, die dem Ziel-Ereignisstrom zugeordnet sind. Dies ist in Abbildung 4-9 dargestellt.

A single output table with events already validated and serialized. Note the output_stream entry for routing purposes.
Abbildung 4-9. Eine einzelne Ausgabetabelle mit bereits validierten und serialisierten Ereignissen (beachte den output_stream-Eintrag für Routingzwecke)

Ein Nachteil der Serialisierung vor der Veröffentlichung ist, dass die Leistung aufgrund des Serialisierungs-Overheads leiden kann. Das kann bei leichten Lasten unbedeutend sein, aber bei schwereren Lasten kann es größere Auswirkungen haben. Du musst also sicherstellen, dass deine Leistungsanforderungen erfüllt werden.

Alternativ kann die Serialisierung auch durchgeführt werden, nachdem das Ereignis in die Ausgangstabelle geschrieben wurde, wie in Abbildung 4-8 gezeigt.

Serializing change-data after writing to outbox table, as part of the publishing process
Abbildung 4-10. Serialisierung von Änderungsdaten nach dem Schreiben in die Outbox-Tabelle als Teil des Veröffentlichungsprozesses

Bei dieser Strategie hast du in der Regel unabhängige Ausgangskörbe, einen für jedes Domänenmodell, die dem öffentlichen Schema des entsprechenden Ausgangs-Ereignisstroms zugeordnet sind. Der Publisher-Prozess liest das nicht serialisierte Ereignis aus dem Postausgang und versucht, es mit dem zugehörigen Schema zu serialisieren, bevor es an den Output-Event-Stream übermittelt wird. Abbildung 4-11 zeigt ein Beispiel für mehrere Ausgangskörbe, einen für eine Entität User und einen für eine Entität Account.

Multiple outbox tables. Note that the data is not serialized, which means that it may not be compatible with the schema of the output event stream.
Abbildung 4-11. Mehrere Outbox-Tabellen (beachte, dass die Daten nicht serialisiert sind, was bedeutet, dass sie möglicherweise nicht mit dem Schema des Output-Event-Streams kompatibel sind)

Wenn die Serialisierung fehlschlägt, bedeutet das, dass die Daten des Ereignisses nicht dem definierten Schema entsprechen und daher nicht veröffentlicht werden können. Hier wird die Option Serialisierung nach dem Schreiben schwieriger zu handhaben, da eine bereits abgeschlossene Transaktion inkompatible Daten in die Ausgangstabelle übertragen hat und es keine Garantie gibt, dass die Transaktion rückgängig gemacht werden kann.

In der Realität wirst du in der Regel eine große Anzahl von nicht serialisierbaren Ereignissen in deinem Postausgang vorfinden. Höchstwahrscheinlich wird ein menschliches Eingreifen erforderlich sein, um einige der Daten zu retten, aber die Behebung des Problems ist zeitaufwändig und schwierig und kann sogar Ausfallzeiten erfordern, um weitere Probleme zu vermeiden. Erschwerend kommt hinzu, dass einige Ereignisse tatsächlich kompatibel sein können und bereits veröffentlicht wurden, was zu einer falschen Reihenfolge der Ereignisse in den Ausgabeströmen führen kann.

Tipp

Die Serialisierung vor dem Ereignis bietet eine bessere Garantie gegen inkompatible Daten als die Serialisierung nach dem Ereignis und verhindert die Weitergabe von Ereignissen, die den Datenvertrag verletzen. Der Nachteil ist, dass diese Implementierung auch verhindert, dass der Geschäftsprozess abgeschlossen wird, wenn die Serialisierung fehlschlägt, da die Transaktion zurückgesetzt werden muss.

Die Validierung und Serialisierung vor dem Schreiben stellt sicher, dass die Daten als Bürger erster Klasse behandelt werden und bietet die Garantie, dass die Ereignisse im ausgegebenen Ereignisstrom letztendlich mit den Daten im Quelldatenspeicher übereinstimmen, während gleichzeitig die Isolierung des internen Datenmodells der Quelle erhalten bleibt. Dies ist die stärkste Garantie, die eine Lösung zur Erfassung von Änderungsdaten bieten kann.

Vorteile der Event-Produktion mit Outbox-Tabellen

Das Erzeugen von Ereignissen über Ausgangstabellen bietet eine Reihe von bedeutenden Vorteilen:

Mehrsprachige Unterstützung

Dieser Ansatz wird von jedem Client oder Framework unterstützt, das transaktionale Funktionen zur Verfügung stellt.

Durchsetzung des Schemas vor der Tat

Schemata können durch Serialisierung validiert werden, bevor sie in die Ausgangstabelle eingefügt werden.

Isolierung des internen Datenmodells

Die Entwickler von Datenspeicheranwendungen können auswählen, welche Felder in die Ausgangstabelle geschrieben werden sollen, wobei die internen Felder isoliert bleiben.

Denormalisierung

Die Daten können bei Bedarf denormalisiert werden, bevor sie in die Postausgangstabelle geschrieben werden.

Nachteile der Eventproduktion mit Outbox-Tabellen

Das Erzeugen von Ereignissen über Ausgangstabellen hat auch einige Nachteile:

Erforderliche Änderungen am Anwendungscode

Um dieses Muster zu aktivieren, muss der Anwendungscode geändert werden, was Entwicklungs- und Testressourcen bei den Anwendungsbetreuern erfordert.

Auswirkungen auf die Leistung von Geschäftsprozessen

Die Auswirkungen auf die Leistung des Geschäftsablaufs können nicht unerheblich sein, insbesondere bei der Validierung von Schemata durch Serialisierung. Fehlgeschlagene Transaktionen können auch verhindern, dass die Geschäftsabläufe fortgesetzt werden.

Auswirkungen auf die Leistung des Datenspeichers

Die Auswirkungen auf die Leistung des Datenspeichers können nicht unerheblich sein, vor allem wenn eine große Anzahl von Datensätzen geschrieben, gelesen und aus demPostausgang gelöscht wird.

Hinweis

Die Auswirkungen auf die Leistung müssen gegen andere Kosten abgewogen werden. Manche Unternehmen geben beispielsweise einfach Ereignisse aus, indem sie die Protokolle der Änderungsdatenerfassung analysieren, und überlassen es den nachgelagerten Teams, die Ereignisse im Nachhinein zu bereinigen. Dies verursacht eine Reihe von Kosten in Form von Rechenkosten für die Verarbeitung und Standardisierung der Ereignisse sowie Personalkosten für die Lösung inkompatibler Schemata und die Berücksichtigung der Auswirkungen einer starken Kopplung an interne Datenmodelle. Die Kosten, die auf der Produzentenseite eingespart werden, werden oft durch die Kosten, die auf der Konsumentenseite für die Bewältigung dieser Probleme anfallen, in den Schatten gestellt.

Erfassen von Änderungsdaten mit Auslösern

Die Unterstützung von Triggern geht vielen der in den vorangegangenen Abschnitten untersuchten Auditing-, Binlog- und Write-Ahead-Log-Muster voraus. Viele ältere relationale Datenbanken verwenden Trigger, um Prüfungstabellen zu erstellen. Wie der Name schon sagt, sind Trigger so eingerichtet, dass sie bei einer bestimmten Bedingung automatisch ausgelöst werden. Wenn der Trigger fehlschlägt, schlägt auch der Befehl fehl, der die Ausführung des Triggers ausgelöst hat, so dass eine atomare Aktualisierung gewährleistet ist.

Du kannst Änderungen auf Zeilenebene in einer Prüftabelle erfassen, indem du einen AFTER Trigger verwendest. Zum Beispiel schreibt der Trigger nach jedem INSERT, UPDATE oder DELETE Befehl eine entsprechende Zeile in die Tabelle mit den Änderungsdaten. So wird sichergestellt, dass Änderungen an einer bestimmten Tabelle entsprechend nachverfolgt werden.

Betrachte das in Abbildung 4-12 gezeigte Beispiel. Benutzerdaten werden in eine Benutzertabelle eingefügt, wobei ein Trigger die Ereignisse erfasst, sobald sie auftreten. Beachte, dass der Trigger auch die Zeit erfasst, zu der das Einfügen stattgefunden hat, sowie eine automatisch aufsteigende Sequenz-ID, die der Event-Publisher-Prozess verwenden kann.

A trigger being used to capture changes to a User table
Abbildung 4-12. Verwendung eines Triggers zur Erfassung von Änderungen an einer Benutzertabelle

In der Regel kannst du die Änderungsdaten während der Ausführung eines Triggers nicht mit dem Ereignisschema abgleichen, obwohl das nicht unmöglich ist. Ein Hauptproblem ist, dass es möglicherweise einfach nicht unterstützt wird, da Trigger innerhalb der Datenbank selbst ausgeführt werden und viele Sprachen, die sie unterstützen, begrenzt sind. Während PostgreSQL C, Python und Perl unterstützt, mit denen benutzerdefinierte Funktionen zur Schemaüberprüfung geschrieben werden können, bieten viele andere Datenbanken keine Mehrsprachenunterstützung. Und selbst wenn ein Trigger unterstützt wird, kann es sein, dass er einfach zu teuer ist. Jeder Trigger wird unabhängig ausgelöst und erfordert einen nicht unerheblichen Aufwand, um die erforderlichen Daten, Schemata und die Validierungslogik zu speichern, und für viele Systemlasten sind die Kosten zu hoch.

Abbildung 4-13 zeigt eine Fortsetzung des vorherigen Beispiels. Die Änderungsdaten werden im Nachhinein validiert und serialisiert, wobei erfolgreich validierte Daten in den Ausgangs-Ereignisstrom ausgegeben werden. Daten, die nicht erfolgreich sind, müssen entsprechend den Geschäftsanforderungen behandelt werden, erfordern aber wahrscheinlich ein menschlichesEingreifen.

Das Tabellenschema für die Änderungsdatenerfassung ist die Brücke zwischen dem internen Tabellenschema und dem Schema für den Output-Event-Stream. Die Kompatibilität zwischen allen drei Schemata ist wichtig, um sicherzustellen, dass die Daten in den Output-Event-Stream übertragen werden können. Da die Validierung des Ausgabeschemas in der Regel nicht während der Ausführung des Triggers erfolgt, ist es am besten, wenn die Änderungsdatentabelle mit dem Format des Ausgabe-Ereignisschemas synchronisiert ist.

After-the-fact serialization and production to the output event stream
Abbildung 4-13. Nachträgliche Validierung und Produktion für den Output-Event-Stream
Tipp

Vergleiche das Format des Ausgabe-Ereignisschemas mit der Änderungsdatentabelle während der Tests. Dadurch können Inkompatibilitäten vor dem Produktionseinsatz aufgedeckt werden.

Abgesehen davon können Trigger in vielen Altsystemen hervorragend funktionieren. Legacy-Systeme verwenden in der Regel per definitionem alte Technologien; Trigger gibt es schon sehr lange und sie können sehr wohl in der Lage sein, den notwendigen Mechanismus zur Erfassung von Änderungsdaten zu liefern. Die Zugriffs- und Belastungsmuster sind in der Regel gut definiert und stabil, so dass die Auswirkungen der Einführung von Triggern genau abgeschätzt werden können. Schließlich ist es zwar unwahrscheinlich, dass während des Triggering-Prozesses selbst eine Schema-Validierung stattfindet, aber ebenso unwahrscheinlich ist es, dass sich die Schemata selbst ändern, da es sich um ein Legacy-System handelt. Eine nachträgliche Validierung ist nur dann ein Problem, wenn sich die Schemata voraussichtlich häufig ändern werden.

Warnung

Versuche, die Verwendung von Triggern zu vermeiden, wenn du stattdessen modernere Funktionen für die Erzeugung von oder den Zugriff auf Änderungsdaten nutzen kannst. Du solltest den Leistungs- und Verwaltungsaufwand für eine Trigger-basierte Lösung nicht unterschätzen, vor allem wenn viele Dutzend oder Hunderte von Tabellen und Datenmodellen beteiligt sind.

Vorteile der Verwendung von Triggern

Die Vorteile der Verwendung von Triggern sind folgende

Unterstützt von den meisten Datenbanken

Trigger gibt es für die meisten relationalen Datenbanken.

Geringer Overhead für kleine Datensätze

Die Wartung und Konfiguration ist bei einer kleinen Anzahl von Datensätzen relativ einfach.

Anpassbare Logik

Der Triggercode kann so angepasst werden, dass nur eine Teilmenge bestimmter Felder offengelegt wird. Auf diese Weise lässt sich genau festlegen, welche Daten den nachgelagerten Verbrauchern zugänglich gemacht werden.

Nachteile der Verwendung von Triggern

Einige Nachteile der Verwendung von Triggern sind:

Performance Overhead

Trigger werden inline mit Aktionen in den Datenbanktabellen ausgeführt und können nicht unerhebliche Verarbeitungsressourcen verbrauchen. Je nach den Leistungsanforderungen und SLAs deiner Dienste kann dieser Ansatz zu einer inakzeptablen Belastung führen.

Komplexität des Änderungsmanagements

Änderungen am Anwendungscode und an den Datensatzdefinitionen können entsprechende Änderungen der Trigger erfordern. Notwendige Änderungen an den zugrunde liegenden Triggern können von den Systembetreuern übersehen werden, was zu Datenfreigabeergebnissen führt, die nicht mit den internen Datensätzen übereinstimmen. Es sollten umfassende Tests durchgeführt werden, um sicherzustellen, dass die Trigger-Workflows wie erwartet funktionieren.

Schlechte Skalierung

Die Anzahl der benötigten Trigger skaliert linear mit der Anzahl der zu erfassenden Datensätze. Zusätzliche Trigger, die bereits in der Geschäftslogik vorhanden sind, wie z. B. solche, die für die Erzwingung von Abhängigkeiten zwischen Tabellen verwendet werden, sind dabei nicht berücksichtigt.

Schema-Durchsetzung im Nachhinein

Die Schemaerzwingung für das Ausgabeereignis erfolgt erst, nachdem der Datensatz in der Postausgangstabelle veröffentlicht wurde. Dies kann dazu führen, dass Ereignisse in der Ausgangstabelle nicht veröffentlicht werden können.

Tipp

Einige Datenbanken ermöglichen die Ausführung von Triggern mit Sprachen, die während der Ausführung des Triggers die Kompatibilität mit den Schemata der Ausgabeereignisse überprüfen können (z. B. Python für PostgreSQL). Das kann die Komplexität und den Aufwand erhöhen, verringert aber das Risiko von Schema-Inkompatibilitäten in der Folgezeit erheblich.

Änderungen an der Datendefinition von Datensätzen während der Erfassung vornehmen

Die Integration von Änderungen der Datendefinition kann in einem Datenbefreiungs-Framework schwierig sein. Datenmigrationen sind ein gängiger Vorgang für viele relationale Datenbankanwendungen und müssen durch die Erfassung unterstützt werden. Änderungen an der Datendefinition einer relationalen Datenbank können das Hinzufügen, Löschen und Umbenennen von Spalten, das Ändern des Typs einer Spalte und das Hinzufügen oder Entfernen von Standardwerten umfassen. Alle diese Vorgänge sind zwar gültige Änderungen an Datensätzen, können aber Probleme bei der Produktion von Daten für befreite Ereignisströme verursachen.

Hinweis

DieDatendefinition ist die formale Beschreibung des Datensatzes. Eine Tabelle in einer relationalen Datenbank wird zum Beispiel mit einer Datendefinitionssprache (DDL) definiert. Die sich daraus ergebende Tabelle, die Spalten, Namen, Typen und Indizes sind alle Teil der Datendefinition.

Wenn zum Beispiel eine vollständige Kompatibilität der Schemaentwicklung erforderlich ist, kannst du eine Spalte ohne Standardwert nicht aus dem zu erfassenden Datensatz entfernen, da Verbraucher, die das zuvor definierte Schema verwenden, einen Wert für dieses Feld erwarten. Die Verbraucher könnten nicht auf einen Standardwert zurückgreifen, weil bei der Vertragsdefinition keiner festgelegt wurde, so dass sie sich in einem unklaren Zustand befinden würden. Wenn eine inkompatible Änderung unbedingt notwendig ist und ein Bruch des Datenvertrags unvermeidlich ist, müssen sich Datenproduzent und -verbraucher auf einen neuen Datenvertrag einigen.

Vorsicht

Gültige Änderungen an dem zu erfassenden Datensatz sind möglicherweise keine gültigen Änderungen für das freigegebene Ereignisschema. Diese Inkompatibilität führt zu fehlerhaften Schemaänderungen, die sich auf alle nachgelagerten Verbraucher des Ereignisstroms auswirken.

Die Erfassung von DDL-Änderungen hängt von dem Integrationsmuster ab, das zur Erfassung der Änderungsdaten verwendet wird. Da DDL-Änderungen erhebliche Auswirkungen auf die nachgelagerten Datenkonsumenten haben können, ist es wichtig zu bestimmen, ob deine Erfassungsmuster DDL-Änderungen vor oder nach dem Ereignis erkennen. Das Abfragemuster und das CDC-Protokollmuster können DDL-Änderungen zum Beispiel nur im Nachhinein erkennen, d.h. wenn sie bereits auf den Datensatz angewendet wurden. Das Muster der Änderungsdatentabelle hingegen ist in den Entwicklungszyklus des Quellsystems integriert, so dass Änderungen am Datensatz vor der Produktionsfreigabe mit der Änderungsdatentabelle validiert werden müssen.

Umgang mit nachträglichen Datendefinitionsänderungen für die Abfrage- und CDC-Log-Muster

Für das Abfragemuster kann das Schema zur Abfragezeit abgefragt und ein Ereignisschema abgeleitet werden. Das neue Ereignisschema kann mit dem Schema des ausgegebenen Ereignisstroms verglichen werden, wobei Schemakompatibilitätsregeln verwendet werden, um die Veröffentlichung der Ereignisdaten zu erlauben oder zu verbieten. Dieser Mechanismus der Schemaerstellung wird von zahlreichen Abfragekonnektoren verwendet, wie z. B. denjenigen, die mit dem Kafka ConnectFramework bereitgestellt werden.

Beim CDC-Protokollmuster werden Aktualisierungen der Datendefinition normalerweise in einem eigenen Teil des CDC-Protokolls erfasst. Diese Änderungen müssen aus den Protokollen extrahiert und in ein für den Datensatz repräsentatives Schema abgeleitet werden. Sobald das Schema erstellt ist, kann es anhand des nachgelagerten Ereignisschemas validiert werden. Die Unterstützung für diese Funktion ist jedoch begrenzt. Derzeit unterstützt der Debezium-Konnektor nur die Änderungen der MySQL-Datendefinition.

Umgang mit Datendefinitionsänderungen für Änderungsdaten-Tabellenerfassungsmuster

Die Änderungsdatentabelle fungiert als Brücke zwischen dem Schema des Output-Event-Streams und dem internen Statusschema. Unvereinbarkeiten im Validierungscode der Anwendung oder in der Triggerfunktion der Datenbank verhindern, dass die Daten in die Änderungsdatentabelle geschrieben werden, und der Fehler wird auf dem Stapel zurückgeschickt. Änderungen an der Tabelle zur Erfassung von Änderungsdaten erfordern eine Schemaentwicklung, die mit dem Ausgangs-Ereignisstrom kompatibel ist, entsprechend den Regeln für die Schemakompatibilität. Dies ist ein zweistufiger Prozess, der die Wahrscheinlichkeit, dass ungewollte Änderungen in die Produktion gelangen, deutlich verringert.

Versenken von Ereignisdaten in Datenspeichern

Das Abrufen von Daten aus Ereignisströmen besteht darin, Ereignisdaten zu konsumieren und sie in einen Datenspeicher einzufügen. Dies wird entweder durch das zentralisierte Framework oder durch einen eigenständigen Microservice ermöglicht. Jede Art von Ereignisdaten, seien es Entitäten, verschlüsselte oder nicht verschlüsselte Ereignisse, kann in einem Datenspeicher abgelegt werden.

Event Sinking ist besonders nützlich für die Integration von nicht ereignisgesteuerten Anwendungen mit Ereignisströmen. Der Sink-Prozess liest die Ereignisströme vom Event Broker und fügt die Daten in den angegebenen Datenspeicher ein. Er behält seine eigenen Verbrauchsoffsets im Auge und schreibt die Ereignisdaten, sobald sie am Eingang ankommen, wobei er völlig unabhängig von der nicht ereignisgesteuerten Anwendung handelt.

Eine typische Anwendung von Event Sinking ist der Ersatz von direkten Punkt-zu-Punkt-Kopplungen zwischen Altsystemen. Sobald die Daten des Quellsystems in Ereignisströme umgewandelt wurden, können sie mit wenigen Änderungen in das Zielsystem eingespeist werden. Der Sink-Prozess arbeitet sowohl extern als auch unsichtbar für das Zielsystem.

Data Sinking wird auch häufig von Teams eingesetzt, die Big-Data-Analysen im Stapelverfahren durchführen müssen. Dazu werden die Daten normalerweise in ein Hadoop Distributed File System gesenkt, das Big-Data-Analysetools bereitstellt.

Wenn du eine gemeinsame Plattform wie Kafka Connect verwendest, kannst du Sinks mit einfachen Konfigurationen festlegen und sie auf der gemeinsamen Infrastruktur betreiben. Eigenständige Microservice-Sinks bieten eine alternative Lösung. Die Entwickler können sie auf der Microservice-Plattform erstellen und betreiben und sie unabhängig verwalten.

Die Auswirkungen von Sinking und Sourcing auf ein Unternehmen

Ein zentralisierter Rahmen ermöglicht einen geringeren Aufwand bei der Datenfreigabe. Dieses Framework kann von einem einzigen Team in großem Umfang betrieben werden, das wiederum die Datenfreigabebedürfnisse anderer Teams im Unternehmen unterstützt. Die Teams, die Daten integrieren wollen, müssen sich dann nur um die Konfiguration und das Design der Konnektoren kümmern, nicht aber um die operativen Aufgaben. Dieser Ansatz eignet sich am besten für größere Organisationen, in denen Daten in mehreren Datenspeichern und in mehreren Teams gespeichert sind, da er einen schnellen Einstieg in die Datenfreigabe ermöglicht, ohne dass jedes Team eine eigene Lösung entwickeln muss.

Es gibt zwei Hauptfallen, in die du tappen kannst, wenn du ein zentralisiertes Framework verwendest. Erstens sind die Verantwortlichkeiten für die Datenbeschaffung und -senkung nun auf mehrere Teams verteilt. Das Team, das das zentralisierte Framework betreibt, ist für die Stabilität, die Skalierung und den Zustand des Frameworks und der einzelnen Konnektoren verantwortlich. In der Zwischenzeit ist das Team, das das zu erfassende System betreibt, unabhängig und kann Entscheidungen treffen, die die Leistung und Stabilität des Konnektors verändern, z. B. das Hinzufügen und Entfernen von Feldern oder das Ändern der Logik, die sich auf das Datenvolumen auswirkt, das über den Konnektor übertragen wird. Dies führt zu einer direkten Abhängigkeit zwischen diesen beiden Teams. Diese Änderungen können die Konnektoren zerstören, werden aber möglicherweise nur vom Konnektormanagementteam erkannt, was zu linear skalierenden, teamübergreifenden Abhängigkeiten führt. Dies kann zu einer schwer zu bewältigenden Belastung werden, wenn die Zahl der Änderungen steigt.

Das zweite Problem ist etwas weitreichender, vor allem in Unternehmen, in denen die ereignisgesteuerten Prinzipien nur teilweise übernommen wurden. Systeme können sich zu sehr auf Frameworks und Konnektoren verlassen, die die ereignisgesteuerte Arbeit für sie erledigen. Wenn die Daten erst einmal aus den internen Statusspeichern befreit und in Ereignisströmen veröffentlicht wurden, kann es passieren, dass das Unternehmen selbstzufrieden wird, wenn es zu Microservices übergeht. Die Teams verlassen sich zu sehr auf das Connector-Framework, um Daten zu beschaffen und zu verteilen, und beschließen, ihre Anwendungen nicht in native ereignisgesteuerte Anwendungen umzuwandeln. In diesem Fall ziehen sie es vor, bei Bedarf einfach neue Quellen und Senken anzufordern, ohne dass ihre gesamte zugrunde liegende Anwendung von Ereignissen beeinflusst wird.

Warnung

CDC-Tools sind nicht das endgültige Ziel bei der Umstellung auf eine ereignisgesteuerte Architektur, sondern dienen in erster Linie dazu, den Prozess in Gang zu bringen. Der eigentliche Wert des Event-Brokers als Datenübertragungsschicht liegt in der Bereitstellung einer robusten, zuverlässigen und wahrheitsgetreuen Quelle für Ereignisdaten, die von den Implementierungsschichten entkoppelt ist, und der Broker ist nur so gut wie die Qualität und Zuverlässigkeit seiner Daten.

Beide Probleme können durch ein richtiges Verständnis der Rolle des Frameworks für die Erfassung von Änderungsdaten entschärft werden. Vielleicht ist es kontraintuitiv, die Nutzung des CDC-Frameworks zu minimieren und die Teams ihre eigene Änderungsdatenerfassung (z. B. nach dem Outbox-Muster) implementieren zu lassen, obwohl dies zusätzliche Vorarbeiten erfordert. Die Teams sind allein für die Veröffentlichung und die Ereignisse in ihrem System verantwortlich, wodurch teamübergreifende Abhängigkeiten und eine brüchige, auf Konnektoren basierende CDC vermieden werden. Dadurch wird der Arbeitsaufwand für das CDC-Framework-Team minimiert und es kann sich auf die Unterstützung der Produkte konzentrieren, die es wirklich brauchen.

Die Verringerung der Abhängigkeit vom CDC-Framework propagiert auch eine "Event-First"-Denkweise. Anstatt die Ereignisströme als eine Möglichkeit zu betrachten, Daten zwischen Monolithen zu verschieben, betrachtest du jedes System als direkten Herausgeber und Verbraucher von Ereignissen und nimmst so am ereignisgesteuerten Ökosystem teil. Indem du ein aktiver Teilnehmer im EDM-Ökosystem wirst, fängst du an, darüber nachzudenken, wann und wie das System Ereignisse produzieren muss, über die Daten da draußen und nicht nur über die Daten hier drinnen. Dies ist ein wichtiger Teil des kulturellen Wandels hin zu einer erfolgreichen Umsetzung von EDM.

Für Produkte mit begrenzten Ressourcen und solche, die nur gewartet werden, kann ein zentralisiertes Source- und Sink-Connector-System ein großer Segen sein. Für andere Produkte, vor allem solche, die komplexer sind, einen hohen Bedarf an Ereignisströmen haben und sich in aktiver Entwicklung befinden, ist die laufende Wartung und Unterstützung von Konnektoren nicht tragbar. Unter diesen Umständen ist es am besten, Zeit für das Refactoring der Codebasis einzuplanen, damit die Anwendung zu einer wirklich nativen ereignisgesteuerten Anwendung wird.

Und schließlich solltest du sorgfältig abwägen, welche Kompromisse die einzelnen CDC-Strategien mit sich bringen. Dies ist oft ein Diskussions- und Streitpunkt innerhalb einer Organisation, da die Teams versuchen, ihre neuen Verantwortlichkeiten und Grenzen in Bezug auf die Produktion ihrer Ereignisse als einzige Quelle der Wahrheit herauszufinden. Der Übergang zu einer ereignisgesteuerten Architektur erfordert Investitionen in die Datenkommunikationsschicht, und der Nutzen dieser Schicht kann immer nur so gut sein wie die Qualität der darin enthaltenen Daten. Jeder in der Organisation muss umdenken, um die Auswirkungen seiner freigegebenen Daten auf den Rest der Organisation zu berücksichtigen und klare Service-Level-Vereinbarungen hinsichtlich der Schemata, Datenmodelle, Reihenfolge, Latenz und Korrektheit der von ihm produzierten Ereignisse zu treffen.

Zusammenfassung

Die Datenfreigabe ist ein wichtiger Schritt auf dem Weg zu einer ausgereiften und zugänglichen Datenkommunikationsschicht. Altsysteme enthalten häufig den Großteil der Kerngeschäftsmodelle, die in einer Art zentralisierter Implementierungskommunikationsstruktur gespeichert sind. Diese Daten müssen von diesen Altsystemen befreit werden, damit andere Bereiche des Unternehmens neue, entkoppelte Produkte und Dienstleistungen erstellen können.

Es gibt eine Reihe von Frameworks, Tools und Strategien, um Daten aus ihren Implementierungsdatenbeständen zu extrahieren und umzuwandeln. Jede hat ihre eigenen Vorteile, Nachteile und Kompromisse. Welche Optionen du auswählst, hängt von deinen Anwendungsfällen ab, oder du musst deine eigenen Mechanismen und Prozesse entwickeln.

Das Ziel der Datenbefreiung ist es, eine saubere und konsistente Single Source of Truth für wichtige Daten im Unternehmen zu schaffen. Der Zugriff auf die Daten wird von ihrer Produktion und Speicherung entkoppelt, so dass die Kommunikationsstrukturen der Implementierung nicht mehr doppelt genutzt werden müssen. Dieser einfache Akt reduziert die Grenzen für den Zugriff auf wichtige Bereichsdaten aus den zahlreichen Implementierungen von Altsystemen und fördert direkt die Entwicklung neuer Produkte und Dienstleistungen.

Es gibt ein ganzes Spektrum von Strategien zur Datenfreigabe. Auf der einen Seite gibt es eine sorgfältige Integration mit dem Quellsystem, bei der Ereignisse an den Event Broker gesendet werden, während sie in den Datenspeicher der Implementierung geschrieben werden. Einige Systeme sind sogar in der Lage, zuerst in den Ereignisstrom zu produzieren, bevor sie ihn für ihre eigenen Bedürfnisse nutzen, was den Ereignisstrom als einzige Quelle der Wahrheit noch weiter stärkt. Der Produzent ist sich seiner Rolle als guter Datenproduzent bewusst und trifft Vorkehrungen, um unbeabsichtigte Änderungen zu verhindern. Die Produzenten versuchen, mit den Konsumenten zusammenzuarbeiten, um einen qualitativ hochwertigen, klar definierten Datenstrom zu gewährleisten, störende Änderungen zu minimieren und sicherzustellen, dass Änderungen am System mit den Schemata der Ereignisse, die sie produzieren, kompatibel sind.

Am anderen Ende des Spektrums findest du die sehr reaktiven Strategien. Die Eigentümer der Quelldaten in der Implementierung haben wenig bis gar keinen Einblick in die Produktion von Daten für den Event Broker. Sie verlassen sich voll und ganz auf Frameworks, die die Daten entweder direkt aus ihren internen Datensätzen ziehen oder die Protokolle zur Erfassung von Änderungsdaten analysieren. Kaputte Schemata, die die nachgelagerten Verbraucher stören, sind keine Seltenheit, ebenso wenig wie die Offenlegung interner Datenmodelle der Quellimplementierung. Dieses Modell ist auf lange Sicht nicht tragbar, da es die Verantwortung des Dateneigentümers vernachlässigt, eine saubere, konsistente Produktion von Domain-Events sicherzustellen.

Die Unternehmenskultur bestimmt, wie erfolgreich die Initiativen zur Datenfreigabe auf dem Weg zu einer ereignisgesteuerten Architektur sein werden. Die Dateneigentümer müssen die Notwendigkeit, saubere und zuverlässige Ereignisströme zu erzeugen, ernst nehmen und verstehen, dass Datenerfassungsmechanismen als Endziel für die Freigabe von Ereignisdaten unzureichend sind.

Get Aufbau ereignisgesteuerter Microservices now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.