In einem Versuch, die Ideen hinter 'Life Beyond Distributed Transactions, an Apostate's Opion.' von Pat Helland besser zu verstehen, werde ich versuchen zu erklären, wie das Konzept für ein altehrwürdiges Beispiel funktionieren würde: die gute alte Überweisung von Geld von einem Konto auf ein anderes, der Archetyp aller verteilten Transaktionen, weil wir absolut sicher sein wollen, dass wir kein Geld verlieren.
Ich bin immer davon ausgegangen, dass Sie eine verteilte Transaktion benötigen, um das Problem des Geldtransfers zu lösen: Das Geld verschwindet an einem Ende des Geschäfts und taucht am anderen Ende wieder auf. Wenn nur eine der beiden Aktionen ausgeführt wird, hat jemand ein Problem. Sie brauchen also eine Transaktion, um den Vorgang abzusichern. Außerdem brauchen Sie eine verteilte Transaktion, weil die Konten verschiedenen Bankensystemen gehören können. Das ist der Punkt, an dem die Realität hinter Ihrem Rücken auftaucht und Sie vor den Kopf stößt: Es wird niemals so etwas wie eine Transaktion geben, die sich über zwei Banken erstreckt. Zumindest nicht in dem Sinne, dass eine Abbuchung von Bank A erst dann sichtbar wird, wenn die entsprechende Gutschrift von Bank B erfolgreich abgeschlossen ist. Es würde einfach zu viel Zeit in Anspruch nehmen, überhaupt etwas abzuschließen, weil es (im wahrsten Sinne des Wortes) Tage dauern kann, bis das Geld von A nach B gelangt. Während dieser ganzen Zeit würde jede Leseoperation des Saldos bei Bank A den Betrag vor dem Abzug anzeigen. Die Arbeit mit verteilten Transaktionen über zwei Bankensysteme hinweg würde ein sprödes System schaffen, bei dem sich die Anwendung oder die Infrastruktur eine möglicherweise große Anzahl von Operationen merken müsste, die noch nicht abgeschlossen sind und zu jedem beliebigen Zeitpunkt zurückgenommen werden könnten. So funktioniert es also nicht, es gibt keine verteilten Transaktionen zwischen Banken. Mit den Ideen in 'Life...' können wir das Überweisungsproblem auf eine generische Art und Weise lösen, die für Überweisungen zwischen Banken und auch für Überweisungen zwischen Konten, die in derselben Datenbank gespeichert sind, funktioniert, da das Problem weit weniger komplex ist. Die Anwendung der im Folgenden beschriebenen Konzepte ist auch dann sinnvoll, wenn nur ein einziges Banksystem beteiligt ist, denn sie ermöglicht es Ihnen, die Anwendung über die Grenzen eines einzelnen Servers hinaus zu skalieren.
Als erstes brauchen wir eine Entität. Im Sinne von 'Leben...' ist eine Entität eine Menge von Daten, die als eine Einheit bestehen bleibt. Das könnte mein Girokonto bei Bank A mit allen Transaktionen sein oder die Daten, die die Bank über mich als Kunden speichern muss. Beachten Sie, dass ich 'mein Girokonto' und 'ich als Kunde' geschrieben habe. Das "mein" und das "mich" sind wichtig, weil der Begriff Entität in diesem Zusammenhang für eine bestimmte Sammlung von Daten verwendet wird. Im traditionellen Sinne von Entity-Relationship-Diagrammen wäre eine Entität der Prototyp für die Erstellung vieler Instanzen von Datenbankeinträgen, ähnlich wie eine Klasse für die Erstellung vieler Objekte verwendet wird. Pat Helland definiert den Begriff Entität neu und meint damit 'eine bestimmte Instanz'. Von der Größe her wäre eine Entität größer als ein Objekt. Kontoentitäten würden wahrscheinlich viele Objekte enthalten, wie z.B. alle Kontobewegungen der letzten zwei Jahre.
Nun zum Geldtransfer. In einer herkömmlichen, nicht verteilten Anwendung würde so etwas passieren: Sperren Sie Konto A; sperren Sie Konto B; nachdem Sie den Saldo geprüft und sichergestellt haben, dass der Abzug in Ordnung ist, ziehen Sie den Betrag von A ab; fügen Sie den Betrag B hinzu; schreiben Sie in den Speicher; bestätigen Sie. Obwohl die Konten in einer einzigen Datenbank gespeichert sind, würde dies als verteilte Transaktion im Sinne des Artikels eingestuft, da sie sich über die Entitäten Konto A und Konto B erstreckt.
Um das Problem zu lösen, könnten Sie einen riesigen IBM-Server kaufen, aber Sie könnten auch Ihre Software-Architektur überdenken und eine Lösung wie die folgende finden (und dann könnten Sie immer noch einen riesigen IBM-Server kaufen, das ist nicht wirklich wichtig).
Die Lösung beruht auf drei Konzepten: Entitäten, Nachrichten und Aktivitäten. Wir haben oben die Entitäten gesehen, also sind als nächstes die Nachrichten an der Reihe. Nachdem wir einen Betrag von Konto A abgezogen haben, muss die Entität, die mit A verbunden ist, der Entität, die Konto B verwaltet, mitteilen, dass sie den Betrag zu Konto B hinzufügen soll. Dies geschieht mit einer Nachricht, die an die Infrastruktur weitergeleitet wird. Entitäten haben zwei Ebenen: eine Geschäftsebene, die sich der Technologie glücklicherweise nicht bewusst ist (sie ist skalenunabhängig), und eine Infrastrukturebene, die sich der Skalierung bewusst ist. Die Infrastrukturebene sendet eine Nachricht an Entität B. Sie beginnt mit dem Versuch, die Nachricht zuzustellen, sobald sie von Entität A empfangen wurde. Die skalenbewusste Schicht von Entität A muss möglicherweise Entität B ausfindig machen, um die Nachricht zuzustellen. Dies kann aus technischen Gründen (Entität B ist aufgrund eines Netzwerkausfalls nicht erreichbar) oder aus geschäftlichen Gründen (das Konto in Entität B ist ungültig) fehlschlagen.
Der Code in Entität A muss mit Fehlern in der Zukunft umgehen: Fehler können einige Zeit nach dem Versenden der Nachricht auftreten. An dieser Stelle kommt das dritte Konzept, Aktivitäten, ins Spiel. Eine Aktivität ist eine Information über die Konversation, die Entität A mit Entität B führt. Sobald Entität A die Nachricht in ihre Warteschlange gestellt hat, speichert die Aktivität diese Tatsache. Wenn eine Erfolgsmeldung von Entität B eingeht, wird die Aktivität geschlossen. Tritt hingegen ein Fehler auf, verfügt Entität A über alle Informationen, die sie zum Handeln benötigt. Eine mögliche Aktion könnte sein, den Betrag dem Konto wieder gutzuschreiben und eine E-Mail an den Kunden zu senden oder eine Fehlermeldung in den Posteingang eines Bankmitarbeiters zu protokollieren. Diese Aktionen können die Zusammenarbeit mit anderen Entitäten erfordern und so zu neuen Nachrichten und neuen Aktivitäten führen, die jeweils in ihrem eigenen Transaktionsbereich arbeiten.
Zusammenfassend haben wir gesehen, dass Entitäten eine Sammlung von Instanzen von Daten oder Objekten sind, die einen gewissen Geschäftssinn haben. Eine Entität hat eine skalenunabhängige und eine skalenbewusste Schicht. Die skalenbewusste Schicht kümmert sich um die Zustellung von Nachrichten an andere Entitäten. Die Scale-Agnostic-Schicht speichert Informationen über Nachrichten, die an eine andere Entität gesendet werden, um mit Fehlern umzugehen, die von der Scale-Agnostic-Schicht nicht automatisch behoben werden können. Eine Transaktion endet mit der Erstellung einer Nachricht. Die Interaktion mit anderen Entitäten ist nie Teil einer Transaktion, da andere Entitäten auf einem anderen Server leben können, der möglicherweise nicht verfügbar ist. Aktivitäten speichern Informationen über den Austausch von Nachrichten zwischen zwei Entitäten und können von der skalenunabhängigen Geschäftsschicht verwendet werden, um Fehler zu behandeln.
Verfasst von

Jan Vermeir
Developing software and infrastructure in teams, doing whatever it takes to get stable, safe and efficient systems in production.
Contact
Let’s discuss how we can support your journey.