Fintech-Engineering-Handbuch
(w.pitula.me)- Systeme, die Geld als zentralen Zustand behandeln, müssen nach den Grundsätzen entworfen werden, keine Daten zu erfinden, keine zu verlieren und nichts zu vertrauen
- Bei der Darstellung von Beträgen sollte man
floatvermeiden und stattdessenBigDecimal, Ganzzahlen in der kleinsten Einheit, rationale Zahlen usw. je nach Verantwortung passend kombinieren; auch die JSON-Serialisierung von Zahlen kann das Problem von IEEE-754 double erneut erzeugen - Ein Ledger muss durch doppelte Buchführung, unveränderliche Audit-Trails, die Trennung von value time, booking time und settlement time sowie durch Korrektur- und Stornobuchungen so aufgebaut sein, dass Salden und Berichte rekonstruierbar bleiben
- Der tatsächliche Geldfluss muss durch Reservierungen, Idempotenz, neustartbare Zustandsmaschinen, Validierung externer APIs und Webhooks, Outbox und CDC sowie Abgleich (reconciliation) vor doppelten Ausgaben und Auslassungen geschützt werden
- Zugriffskontrolle, Vier-Augen-Freigaben, Änderungsverfolgung im SDLC sowie eigenschaftsbasierte Tests und Fault Injection sorgen dafür, dass selbst interne Operatoren und Codeänderungen als Vertrauensgrenze behandelt werden
Grundprinzipien von Fintech-Systemen
- In der Softwareentwicklung, in der Geld das Hauptanliegen des Systems ist, sind Nachvollziehbarkeit, Unveränderlichkeit und Prüfbarkeit weit wichtiger als gewöhnliches CRUD
- Die Zielgruppe sind Menschen, die neu in Fintech einsteigen, bereits im Fintech-Bereich arbeiten oder außerhalb von Fintech verstehen wollen, wie sich Geldsysteme von allgemeinen Systemen unterscheiden
- Alle Muster sind Mittel, um drei Prinzipien einzuhalten
- No invented data: Geld kann nicht aus dem Nichts entstehen, daher dürfen keine Doppelverarbeitungen und keine willkürlichen Saldenänderungen zugelassen werden
- No lost data: Alles, was mit Geld geschieht, muss nachverfolgt und dauerhaft gespeichert werden
- No trust: Externe Anbieter, interne Komponenten und die reale Welt werden nicht blind vertraut, sondern verifiziert
Wie Geld dargestellt wird
- Die Darstellung von Beträgen ist eine der grundlegendsten Entscheidungen in Finanzsystemen; wird sie falsch getroffen, erbt die gesamte darüberliegende Schicht die Fehler
- float/double ist fast nie eine gute Wahl, weil dadurch schwer vorhersagbare Präzisionsverluste entstehen können
- Vorteile sind zwar hohe Geschwindigkeit, Speicherersparnis und dass keine zusätzlichen Bibliotheken oder Datenstrukturen nötig sind
- Typen mit beliebiger Präzision wie
BigDecimalerlauben eine explizite Kontrolle über Rechengenauigkeit und Rundungszeitpunkt- Sie eignen sich für Zwischenrechnungen mit mehreren aufeinanderfolgenden Operationen, etwa bei FX oder Preisberechnungen
- Die Speicherung als Ganzzahl in der kleinsten Einheit entspricht bei den meisten Fiat-Währungen der festen Präzision, wie sie auch in Zentralbanksystemen verwendet wird
- €12.34 werden als
1234gespeichert - Man muss sich an die Stellenzahl von ISO 4217 halten und darf nicht immer von 2 Dezimalstellen ausgehen
- Auch Kryptowährungen verwenden Ganzzahlen der kleinsten Einheit wie satoshi oder wei, aber die Präzision unterscheidet sich je Asset und wird bei Tokens etwa durch
decimalsin ERC-20 definiert - Krypto-Beträge können 64-Bit-Ganzzahlen überschreiten, sodass Ganzzahlen mit beliebiger Breite nötig sein können
- €12.34 werden als
- Rationale Zahlen sind am mächtigsten, wenn kein Präzisionsverlust zulässig ist, aber langsam; zudem ist die Umwandlung in andere Formate ohne Präzisionsverlust schwierig, und meist braucht man dafür benutzerdefinierte Typen oder Bibliotheken
- Speicherformat und Rechenformat sind getrennte Entscheidungen; ein System kann Ganzzahlspeicherung und Zwischenrechnungen mit
BigDecimalgemeinsam verwenden - Auch bei der Serialisierung von Beträgen ist die Behandlung an den Grenzen wichtig
- Gewöhnliche JSON-Zahlen sind in den meisten Parsern IEEE-754-double-Werte, sodass Float-Probleme an der Grenze erneut auftreten, selbst wenn die interne Darstellung sorgfältig gewählt wurde
- Geld sollte als String wie
"12.34"oder als Ganzzahl in der kleinsten Einheit übertragen werden
Rundung und Währungsbehandlung
- Rundung ist bei Division, Währungsumrechnung, Gebühren, Zinsen, der Anwendung von Quoten und beim Verschieben der Präzision unvermeidlich und darf daher nicht implizit bleiben
- Die Rundungsstrategie ist eine Geschäftsentscheidung
- In manchen Fällen muss konservativ nach unten gerundet werden, in anderen kann für statistische Effekte half-even verwendet werden
- Wer die verbleibenden Bruchteile erhält, kann rechtliche und steuerliche Auswirkungen haben
- Man sollte die volle Präzision so lange wie möglich beibehalten und in der Regel nur an Grenzen runden, etwa vor dem Speichern oder vor der Anzeige für Nutzer
- Wird ein Wert in mehrere Teile aufgeteilt und danach gerundet, kann die Summe der Teile vom ursprünglichen Wert abweichen
- Je nach Situation ist dafür ein explizites rounding account nötig
- Geld lässt sich nicht nur durch Zahlen ausdrücken, sondern muss immer zusammen mit der Währung behandelt werden
- Wenn Betrag und Währung in einem Newtype, Struct, einer Klasse oder einem Record wie
Moneyzusammengefasst werden, sinkt die Fehlerwahrscheinlichkeit - Die Addition unterschiedlicher Währungen muss verboten sein; Umrechnungen dürfen nur explizit mit streng kontrollierten Wechselkursen erfolgen
- Beliebige Währungscodes dürfen nicht akzeptiert werden; stattdessen müssen sie an Systemgrenzen gegen eine kontrollierte Menge von Währungen validiert werden
- Fiat-Währungscodes können als Identifikatoren dienen, bei Kryptowährungen ist jedoch oft eine komplexere Kennung wie
(network, contract address)nötig - Pegged, bridged und wrapped Kryptowährungen sind nicht mit dem zugrunde liegenden Asset gleichzusetzen
- Wenn Betrag und Währung in einem Newtype, Struct, einer Klasse oder einem Record wie
FX-Wechselkurse
- Ein FX rate hat immer eine Richtung
- Ein EUR/USD-Kurs ist nicht einfach der Kehrwert von USD/EUR
- Kauf und Verkauf an einer Börse sind wegen des Bid/Ask-Spread Aufträge zu unterschiedlichen Preisen
- Auch der Zeitpunkt des Kurses verändert das Ergebnis
- Ein aktueller Kurs wird verwendet, um den Wert eines aktuellen Bestands oder einer Transaktion zu berechnen, als wäre sie jetzt erfolgt
- Ein Kurs zum Value Date wird für Wertveränderungen oder Steuerberechnungen verwendet
- Bei Umrechnungen sind zwei Arten von Kursen wichtig
- Transactional rate ist der Kurs, zu dem die tatsächliche Umrechnung stattgefunden hat; er ergibt sich aus Ursprungsbetrag und Ergebnisbetrag
- Reference rate wird für Bewertungen und Gleichwertigkeitsbeurteilungen verwendet, etwa beim Bestandswert oder für steuerliche Grundlagen, ist aber kein tatsächlicher Transaktionspreis
- Es gibt keinen standardisierten einheitlichen Wechselkurs
- Wechselkurse entstehen am Markt und unterscheiden sich je nach Handelsplatz oder Berechnungsmethode
- Zentralbankkurse kommen einem Standard am nächsten, können aber nur als reference rate dienen; auch alternative Quellen können gültig sein
- Betrag und Quelle des reference rate müssen gemeinsam gespeichert werden, damit sie später überprüft werden können
Ledger und doppelte Buchführung
- Geldbewegungen müssen so aufgezeichnet werden, dass sie auditierbar sind und sich auch Jahre später noch rekonstruieren lassen
- Doppelte Buchführung ist ein weit verbreitetes Verfahren, bei dem Finanztransaktionen als Liste von Entries in der Form
(credit account, debit account, amount)gespeichert werden- In der klassischen Darstellung gibt es pro Bewegung getrennte Zeilen für Soll und Haben
- Da jede Entry denselben Betrag von einem Konto auf ein anderes verschiebt, bleibt das Ledger stets ausgeglichen
- Geld hat immer eine Quelle und ein Ziel
- Auch externe Anbieter sollten eigene Konten haben, damit Geldflüsse in das System hinein und aus ihm heraus nachverfolgt werden können
- Salden werden nicht gespeichert, sondern aus den Geldbewegungen abgeleitet
- Konten haben Typen wie Asset, Liability und Equity
- Die accounting equation
assets = liabilities + equitybleibt erhalten - In der Praxis braucht man zusätzlich Revenue- und Expense-Konten, um etwa Gebührenerträge oder Verluste durch Abschreibungen zu erfassen
- Die erweiterte Gleichung lautet
assets = liabilities + equity + revenue - expenses
- Die accounting equation
- Eine einzelne Transaktion erzeugt in der Regel mehrere Bewegungen
- Es kann separate Bewegungen für den Nettobetrag und für Gebühren geben
- Gebuchte Entries sind konventionsgemäß unveränderlich; Korrekturen erfolgen durch neue Entries, die den ursprünglichen Eintrag ausgleichen
Zeitmodell: value, booking, settlement
- Transaktionen haben meist zwei oder mehr, manchmal drei Zeitstempel
- Value time: der Zeitpunkt, zu dem die Transaktion tatsächlich stattgefunden hat
- Booking time: der Zeitpunkt, zu dem sie im System erfasst wurde
- Settlement time: der Zeitpunkt, zu dem das Geld tatsächlich übertragen oder realisiert wurde
- Settlement time existiert nicht bei allen Transaktionen und wird meist als T+X ausgedrückt
- T+2 bedeutet, dass das Settlement zwei Tage nach dem Value Date erfolgt
- Value time und booking time weichen fast immer voneinander ab
- Wenn booking > value, ist die Buchung rückdatiert; das ist besonders wichtig, wenn Berichtszeiträume unterschiedlich sind
- Wenn booking < value, ist sie vordatiert; das tritt bei reservierten Zahlungen oder Zahlungen mit zukünftigem Datum auf
- Ein Beispiel für eine Kartenzahlung: Die Zahlung erfolgt zu T1, wird vom System zu T2 erfasst, und zu T3 überweist der Zahlungsanbieter das Geld auf das Konto
- Geschäftsberichte orientieren sich meist an value time oder settlement time, während booking time für die Nachvollziehbarkeit nützlich ist
- Wenn mehrere Zeitpunkte in einem einzigen
created_atzusammengezogen werden, gehen Informationen verloren, die sich später nicht mehr rekonstruieren lassen
Audit-Trail, Event Sourcing, Unveränderlichkeit
- Finanzsysteme unterliegen regulatorischen Audits und müssen möglicherweise nachweisen, ob Nutzer- und Unternehmensgelder vermischt wurden, ob Erträge erklärbar sind, ob die nach außen gegebenen Informationen mit der Realität übereinstimmen und ob Gelder geschützt sind
- Ein audit trail ist nicht nur der aktuelle Zustand, sondern die vollständige Historie, wie dieser Zustand entstanden ist
- was passiert ist
- wann es passiert ist
- wer oder was es ausgelöst hat
- warum es passiert ist
- Nicht nur Geldbewegungen, sondern auch manuelle Eingriffe, Änderungen an Konfigurationen wie fee schedule, rate source und limits sowie Berechtigungsänderungen benötigen einen Audit-Trail
- Bei Entscheidungen wie compliance check oder risk score kann es unzureichend sein, nur das Ergebnis zu speichern
- Wenn sie in einer decision table oder einer rules engine wie DMN, Drools oder Decisions4s liegen, entsteht eine Struktur, in der reproduzierbar ist, welche Regeln mit welchen Eingaben ausgeführt wurden und welches Ergebnis daraus entstand
- Event sourcing ist ein systematischer Ansatz zur Erstellung eines Audit-Trails
- Aktueller Zustand und Log werden nicht getrennt gespeichert; stattdessen werden nur Events gespeichert, aus denen der Zustand abgeleitet wird
- Ein Ledger mit doppelter Buchführung ist ein Beispiel für dieses Muster, da es balances nicht speichert, sondern aus den entries berechnet
- Event Sourcing hat in der Praxis auch erhebliche Einschränkungen
- Es wird nicht überall benötigt; wenn das Ledger Geldflüsse bereits abdeckt, können für angrenzende Domänen ein normales Modell und ein vertrauenswürdiges change log ausreichen
- Für die Performance können balances und projections gecacht oder per Snapshot gesichert werden
- Die Event-Quelle ist oft schwer effizient abfragbar, sodass viel Projektion nötig werden kann
- Events bleiben über Jahre erhalten, daher muss heutiger Code auch sehr alte Events lesen können
- Ein Audit-Trail ist als Beweismittel untauglich, wenn er veränderbar ist, und muss daher append-only sein
- append-only tables, das Entfernen von
UPDATEundDELETEin DB-Berechtigungen, das Blockieren mutierender Operationen in der Anwendungsschicht sowie tamper evidence über checksum oder hash chain sind dafür geeignete Mittel
- append-only tables, das Entfernen von
- In realen Systemen muss man event log oder audit trail wegen Bugs manchmal korrigieren
- In der Regel sollten Daten fixiert sein, sobald sie extern gemeldet wurden; davor kann es möglich sein, Probleme zu finden und direkt zu korrigieren, bevor sie das System verlassen
Storno und Korrektur
- Fehler wie eine Buchung mit falschem Betrag oder auf das falsche Konto kommen vor
- Unveränderlichkeit verlangt, nach vorne korrigierend zu arbeiten
- Man bucht einen neuen kompensierenden Eintrag und verknüpft ihn in beide Richtungen mit dem ursprünglichen Datensatz
- Reversal gleicht das Original wirtschaftlich vollständig aus, als hätte es nie stattgefunden, aber sowohl Original als auch Reversal bleiben in der Historie erhalten
- Correction oder adjustment bucht entweder die Differenz zwischen dem tatsächlich erfassten und dem korrekten Wert oder storniert zuerst und bucht dann mit dem korrekten Wert neu
- Korrekturen können in einen anderen Berichtszeitraum fallen als das Original
- Es muss Verknüpfungsinformationen geben, damit Berichte Korrekturen korrekt zuordnen und tatsächliche Aktivität von Bereinigungen unterscheiden können
- Da Backdating in bereits abgeschlossenen Berichtsperioden meist nicht erlaubt ist, hängt es vom Reporting-Zeitplan ab, ob bei einer Korrektur eine frühere value time gesetzt werden soll
Ausführung von Geldflüssen: Invarianten und Mittelreservierung
- Eine Invariant ist eine Eigenschaft, die im System immer wahr sein muss
- Die accounting equation ist ein Beispiel, und auch fachliche Stakeholder können verschiedene Bedingungen definieren
- Methoden zur Durchsetzung von Invarianten ergänzen sich gegenseitig
- so entwerfen, dass nur gültige Objekte erzeugt werden können
- zur Laufzeit mit Assertions, Tests und property-based testing prüfen
- gespeicherte Daten nachträglich mit reconciliation jobs oder nächtlichen Prüfungen analysieren
- Transaktionen mit Interaktion zur Außenwelt müssen race conditions vermeiden
- Es muss verhindert werden, dass erst nach einem externen Aufruf festgestellt wird, dass das Guthaben nicht reicht, oder dass dasselbe Geld zweimal ausgegeben wird
- Funds reservation oder hold-and-release ist ein Muster, bei dem vor einer externen Interaktion Gelder für eine bestimmte Transaktion reserviert werden
- Bei Erfolg wird die reservation settled und die Transaktion durchgeführt
- Bei Fehlschlag wird sie released und dem available balance wieder zugeschlagen
- Dieses Muster unterscheidet zwischen total balance und available balance
available = total - reserved- Guthabenprüfung und neue reservations erfolgen auf Basis des available balance
- Der endgültige Betrag kann von der vorherigen Schätzung abweichen
- Wenn Gebühren oder Wechselkurse anders ausfallen, reserviert man den erwarteten Betrag, settled dann den tatsächlichen Betrag und released den Rest
- Eine reservation muss immer entweder settled oder released werden
- Verwaiste reservations blockieren Nutzergelder, vernichten oder erzeugen aber kein Geld
- expiry oder timeout können ein Sicherheitsnetz sein, sind aber nicht zwingend erforderlich
- Guthabenprüfung und das Erfassen einer reservation müssen linearizable sein
- Bei stale reads können sonst zwei Transaktionen die Prüfung bestehen und beide auf dieselben Mittel zugreifen
Overdraft und Idempotenz
- Overdraft ist der Zustand, in dem der Kontostand negativ wird
- Ein beabsichtigter overdraft ist ein Kreditprodukt mit Limit und Zinsen und wird meist als separates overdraft-Konto modelliert
- Ein unbeabsichtigter overdraft kann auch dann auftreten, wenn er laut Richtlinie verboten ist
- settlement kann höher ausfallen als die Reservierungsschätzung, oder ein reversal kann eintreffen, nachdem Mittel bereits abgeflossen sind
- funds reservation verkleinert das Zeitfenster, beseitigt es aber nicht
- „Verboten“ und „nicht darstellbar“ sind nicht dasselbe
- Wenn man negative Kontostände mit unsigned integer oder
CHECK (balance >= 0)unmöglich macht, kann das in der Praxis zu Abstürzen, Kappung auf null oder fehlerhafter Verarbeitung führen, obwohl negative Salden verarbeitet werden müssen - Einen negativen Kontostand auf 0 zu kappen erzeugt Geld
- Wenn man negative Kontostände mit unsigned integer oder
- Wenn overdraft erkannt wird, sollte es als Signal für eine Untersuchung behandelt und explizit eingezogen oder verarbeitet werden, etwa über zukünftige Einzahlungen und Verrechnung, Rückzahlungsanforderungen oder write-off auf ein Aufwands-/Verlustkonto
- In verteilten Systemen kann exactly-once delivery nicht garantiert werden; deshalb sind Retries nötig, und Retries können doppelte Zustellung erzeugen
- Idempotency ist die Eigenschaft, dass dieselbe Nachricht auch bei doppelter Zustellung nur einmal Wirkung entfaltet
- Ein expliziter idempotency key ist meist einfacher und sicherer als payload-basierte Deduplizierung
- Der Schlüssel sollte auf eine bestimmte Operation und einen bestimmten Client-Kontext begrenzt sein
- Man muss entscheiden, ob Fehler reproduziert oder neu verarbeitet werden sollen; bei permanenten Fehlern ist Reproduzieren meist einfacher
- Zu prüfen, ob der payload eines doppelten Aufrufs mit dem Original übereinstimmt, ist gute Praxis, bringt aber Implementierungskomplexität und geringere Flexibilität mit sich
- Im großen Maßstab braucht man für die Deduplizierung von Milliarden Requests und bei konkurrierenden Zugriffen eine atomare Barriere
- Ein idempotency window wie 24 Stunden vereinfacht die Implementierung, kostet aber Korrektheit
- Auch Retry-Tests und die Behandlung von out-of-order Retries sind nötig
Neustartfähige Abläufe
- Geldflüsse erstrecken sich über mehrere Schritte, und es muss angenommen werden, dass der Prozess zwischen beliebigen Schritten ausfallen kann
- Full resumability ist ein Entwurfsprinzip, bei dem halbfertige Abläufe immer in einem wiederherstellbaren Zustand gehalten werden
- Fortschrittszustand muss in persistentem Speicher statt nur im Arbeitsspeicher liegen
- Man modelliert den Ablauf als explizite state machine und commitet den Abschluss jedes Schritts, bevor der nächste beginnt
- Es wird ein unabhängiger driver benötigt, der unterbrochene Abläufe erneut anschiebt
- scheduler, worker oder poller müssen auch nach einem orchestrator crash unvollständige Flows weiterverarbeiten können
- Beim Wiederaufnehmen können bereits teilweise ausgeführte Schritte erneut laufen, daher muss jeder Schritt idempotent sein
- Externe Effekte lassen sich nicht zurückrollen
- Nach einem Aufruf der Außenwelt kann man nicht mehr in den Zustand „nicht aufgerufen“ zurückkehren
- Man muss bis zum Abschluss nach vorne durcharbeiten oder bei dauerhaftem Fehler in einem späteren Schritt eine kompensierende Aktion wie im saga pattern buchen
- Man kann eine durable-execution engine wie Temporal, Camunda, Workflows4s oder AWS Step Functions verwenden oder selbst eine persistente state machine bauen
Nutzung externer APIs
- Externe APIs wie von Zahlungsanbietern, Custodians, Blockchain-Nodes oder KYC-Anbietern müssen defensiv behandelt werden, da sich Code, Qualität und Verfügbarkeit nicht kontrollieren lassen
- Dem Schema darf man nicht vertrauen
- Felder können fehlen, Typen sich ändern oder unerwartete null-Werte auftreten
- Wichtige Teile sollten an der Systemgrenze validiert werden, und unerwartete Daten sollten zu einem harten Fehler führen
- Wenn man auch irrelevante Teile validiert, können unnötige Störungen durch Vertragsverletzungen Dritter entstehen
- Bei externen APIs kommen Dinge wie Token in der URL, Präzisionsverlust, HTTP-Codes mit abweichender Bedeutung, Error-Bodys in
200, inkonsistente Pagination oder benutzerdefinierte Datumsformate durchaus vor - Jeder Aufruf kann fehlschlagen, daher braucht es Timeouts und Retries
- Ein Circuit Breaker ist in erster Linie eine Frage der Rücksichtnahme gegenüber überlasteten Servern und erhöht die Komplexität auf Client-Seite
- Er kann aber nötig sein, um endliche Ressourcen wie Latenz, Threads oder Verbindungen zu schützen
- Rate Limits und Quotas sollten vorab kalkuliert werden, damit erwartetes Aufrufvolumen und Provider-Limits zusammenpassen
- Wenn alle Requests und Responses strukturiert und abfragbar gespeichert werden, dienen sie als Grundlage für Untersuchungen, Audit Trails, Belege bei Streitfällen über Provider-Verhalten und Material zur Wiederverarbeitung nach Bugs
- In Kernbereichen kann man Provider-Redundanz in Betracht ziehen
- Etwa durch Datenvalidierung mit zwei Blockchain-Nodes oder einen Backup-Bankpartner, Crypto-Custodian oder KYC-Anbieter
- Die Kosten für Entwicklung, Gebühren und Komplexität sind sehr hoch
- Eine Sandbox kann stark von der Production abweichen, daher sollte man Production-Tests über Canary Releases oder kontrollierte Nutzung mit geringem Impact vorbereiten
Webhook-Verarbeitung
- Webhooks sind ein gängiger Weg, Signale externer Systeme zu empfangen, aber eine sichere Verarbeitung ist nicht einfach
- Man darf keine Reihenfolge annehmen
- Nachrichten können in falscher Reihenfolge eintreffen oder veraltete Daten enthalten
- Man darf den gerade empfangenen Webhook nicht als neuesten Wahrheitsstand ansehen und damit den Status überschreiben
- Man darf keine Gültigkeit annehmen
- Webhooks kommen aus anderen Subsystemen des Issuers und können veraltete oder fehlerhaft transformierte Daten enthalten
- Es ist besser, den Webhook-Body nur als Trigger zu verwenden und den autoritativen Status per API-Abfrage zu prüfen
- Auch die API kann eventually consistent sein, sodass eine sofortige Abfrage noch den vorherigen Status zurückliefert und daher Retries nötig sind
- Man darf keine Zustellung annehmen
- Selbst wenn der Issuer eine starke Redelivery-Policy zusichert, gehen Webhooks irgendwann verloren
- Ein unabhängiger Prozess wie Reconciliation muss die Datenvollständigkeit ergänzend absichern
- Man darf auch keine einmalige Zustellung annehmen
- Derselbe Webhook wird mehrfach zugestellt, und die Verarbeitung muss idempotent sein
- Man sollte schnell bestätigen und asynchron verarbeiten
- Das rohe Event wird in einem dauerhaften Store abgelegt, danach wird sofort ein 2xx zurückgegeben und die eigentliche Arbeit asynchron ausgeführt
- Das Raw Payload sollte unverändert gespeichert werden
- Das ist für stabile Verarbeitung, Audit Trail und Wiederverarbeitung nach Bugs notwendig
- Der Aufrufer muss verifiziert werden
- Üblicherweise versieht der Issuer das Payload mit einer Signatur, und der Empfänger prüft diese per HMAC mit Shared Secret oder per asymmetrischer Signatur auf Basis eines öffentlichen Schlüssels
- Die Signaturprüfung muss auf den empfangenen rohen Bytes erfolgen, nicht auf einem neu serialisierten Payload
- Ein Webhook sollte nicht als Wahrheit darüber behandelt werden, was passiert ist, sondern als Hinweis darauf, dass etwas passiert ist
Vertrauenswürdige Benachrichtigungen: Outbox und CDC
- Wenn Systemänderungen zuverlässig über externe Kanäle wie Kafka-Events oder Webhook-Aufrufe signalisiert werden müssen, wird Transactionality zum Problem
- Es kann passieren, dass das Publish erfolgreich war, aber wegen eines Netzwerkproblems keine Antwort empfangen wurde und deshalb der Systemzustand zurückgerollt wird, oder dass die Zustandsänderung committet wurde, das Publish aber fehlschlägt
- Die Lehrbuchantwort ist 2-Phase Commit oder eine verteilte Transaktion, wird aber wegen der Komplexität und der schwierigen Standardisierung zur Wiederverwendung selten eingesetzt
- Es gibt mehrere pragmatische Optionen
- Outbox-Pattern: Zusammen mit der Zustandsänderung wird die Publishing-Absicht transaktional in einem dedizierten Store festgehalten und später so lange verarbeitet, bis sie erfolgreich ist
- Change Data Capture: Das Write-Ahead- oder Replikationslog der Datenbank wird gelesen, um committete Änderungen in einen Event-Stream umzuwandeln
- Debezium und AWS DMS bieten CDC an
- CDC exportiert Raw Events in Form von Tabellenzeilen, daher ist Postprocessing nötig, um das Durchreichen interner Schemas zu vermeiden
- Listen-to-yourself veröffentlicht zuerst das Event und baut den eigenen Zustand anschließend wieder aus diesem Event auf
- Beim Event Sourcing liegt das Event-Log bereits in der Datenbank vor, sodass von dort publiziert werden kann
- Unabhängig vom gewählten Mechanismus gilt die Zustellung als at-least-once
- Wenn ein Relay oder Connector nach dem Publish, aber vor dem Persistieren des Fortschritts abstürzt, kann beim Neustart erneut gesendet werden
- Consumer müssen über eine stabile Event-ID deduplizieren und idempotent arbeiten
Reconciliation
- Systeme, die von externen Daten abhängen, sind anfällig für Data Drift, also dafür, dass die Zustände zweier Systeme auseinanderlaufen
- Beispielsweise kann ein Webhook verpasst werden, oder eine Transaktion ist im Ledger gebucht, aber im externen Provider-System nicht reflektiert
- Reconciliation ist der Prozess, diese Systeme wieder in Einklang zu bringen
- In der Praxis können es auch mehr als zwei sein, etwa Ledger, Payment Processor und Bank
- Die Cadence kann je nach Kontext und Einschränkungen stündlich, täglich, monatlich oder jährlich sein
- Drift kann aus fehlenden Daten bestehen, aber auch aus komplexeren Abweichungen, etwa wenn Beträge derselben Transaktion unterschiedlich sind
- Auch das Timing ist wichtig
- Wenn das Settlement T+3 erfolgt, kann ein Datensatz drei Tage lang unreconciled sein; das muss im Prozess berücksichtigt werden, um unnötige Alerts zu vermeiden
- Der Matching-Algorithmus ist die zentrale Schwierigkeit
- Wenn die externe Provider-ID intern gespeichert wird, wird das Matching meist einfach
- Andernfalls können Heuristiken auf Basis von Betrag und Zeit nötig sein
- Auch One-to-many-Reconciliation kann erforderlich sein
- Ein einzelner Settlement-Transfer kann mehrere Transaktionen abdecken
- Abweichungen dürfen nicht einfach per Überschreiben so angepasst werden, dass die Reconciliation wieder stimmt
- Die Ursache muss verstanden und behoben werden, mit First-Class-Unterstützung wie Korrekturbuchungen oder Wiederverarbeitung von Webhook-Daten
Kontrolle und Zugriff
- In Geldsystemen muss nicht nur kontrolliert werden, welche Daten existieren, sondern auch, wer welche Aktionen ausführen darf, und im Nachhinein muss sich die Einhaltung von Verfahren nachweisen lassen
- Segregation of duties ist eine Kontrolle, die verhindert, dass eine Person den gesamten Prozess allein beherrscht
- Four-eyes, Maker-Checker oder Dual Control bedeuten, dass eine zweite Person eine bestimmte Aktion freigeben muss, bevor sie wirksam wird
- Das gilt für Aktionen, die Gelder bewegen oder falsch darstellen könnten, etwa große oder manuelle Auszahlungen, manuelle Ledger-Korrekturen, Bewegungen zwischen Treasury und Cold Wallet oder Änderungen an Gebührenplänen oder Limits
- Dieselben Kontrollen gelten auch im Engineering
- Code-Merges, Deployments in Production und Infrastrukturänderungen sind in Geldsystemen sensible Aktionen und erfordern daher Review und Freigabe
- Auch die Freigabe selbst ist Teil des Trails
- Es muss festgehalten werden, wer etwas beantragt hat, wer es freigegeben hat und ob es zwei verschiedene Personen waren, damit sich die Kontrolle nachweisen lässt
- Für Notfälle braucht es einen expliziten und stark auditierbaren Break-Glass-Pfad
- Access Control ist Teil des Systemzustands und verändert sich über die Zeit
- Sowohl Menschen als auch Services sollten nur die minimal nötigen Rechte erhalten
- RBAC ist einzelnen personenbezogenen Berechtigungen vorzuziehen, weil Reviews dadurch einfacher werden
- Das Erteilen und Entziehen von Capabilities sind ebenfalls sensible Events, daher muss protokolliert werden, was, von wem und warum geändert wurde
- Durch planmäßige Access Reviews sollte Permission Drift erkannt werden, also veraltete oder ungenaue Berechtigungen
Nachverfolgung von SDLC-Änderungen
- In regulierten Umgebungen muss der Weg von Code bis in die Produktion auditierbar sein.
- Source Control ist das Änderungsprotokoll.
- Die Commit-History ordnet jede Änderung einem Autor zu und verknüpft sie über Review und verlinkte Tickets mit ihrer Begründung.
- Geschützt werden sollte dies durch signierte Commits, geschützte Branches und ein Verbot von Force-Pushes auf die gemeinsame History.
- Reviews und Pipelines müssen erzwungen werden.
- Wichtig sind Required Reviews, Status Checks und ein Verbot direkter Pushes auf den Main-Branch.
- Deployments müssen nachvollziehbar sein.
- Es muss rekonstruierbar sein, welche Version läuft und wer wann ein Release durchgeführt hat, damit sich Incidents mit der verursachenden Änderung verknüpfen lassen.
Teststrategie
- In Systemen, die mit Geld arbeiten, ist der Raum möglicher Operationssequenzen groß, und interessante Fehler entstehen oft aus Kombinationen, daher sind Tests besonders wichtig.
- Property-based Testing prüft nicht ein bestimmtes Ergebnis, sondern Properties, die für jede Eingabe gelten müssen.
- Das passt gut zu Invarianten und Money Math.
- Beim Erzeugen von Operationssequenzen sollten Invarianten nicht nur am Ende, sondern nach jedem einzelnen Schritt geprüft werden.
- Da sich große Ausführungen schwer manuell durchführen lassen, braucht es ein Testing Harness, das Assertions automatisch injiziert.
- Generative Idempotency Testing verifiziert, dass jede Operation mit Auswirkungen auf die Außenwelt beim zweiten Aufruf keinen Effekt mehr hat.
- Crash-and-Resume-Injection verifiziert, dass sich ein langer Ablauf auch dann erholt, wenn er zwischen beliebigen Schritten abbricht.
- Round-Trip-Testing prüft, ob man nach encode/decode, serialize/deserialize oder convert/convert back wieder beim Ausgangspunkt landet oder innerhalb einer bekannten Toleranz bleibt.
- Das ist ein schneller Weg, um Grenzfälle bei Präzisionsverlusten von Money- und Currency-Typen sowie Serialisierungsfehler zu finden.
- Golden Testing vergleicht Berechnungsergebnisse wie Fee-Breakdowns, Statements oder Reports mit gespeicherten erwarteten Ergebnissen und macht unbeabsichtigte Diffs sichtbar.
- Backward-Compatibility-Testing hält einen Corpus alter Payloads im realen Format vor und verifiziert, dass der aktuelle Code sie weiterhin korrekt deserialisiert und projiziert.
- Production Testing kann nötig sein, wenn sich die Sandbox stark von der Produktion unterscheidet.
- Beispiele sind Canary Releases, kontrollierte Rollouts mit kleinem Blast Radius und Synthetic Transactions, bei denen kontinuierlich kleine reale Beträge fließen.
- Da Production Tests echtes Geld bewegen, müssen sie denselben Ledger-, Reconciliation- und Audit-Trail-Prozess durchlaufen und über die normalen Korrektur-/Reversal-Pfade bereinigt werden.
Domänenbegriffe und Referenzmaterial
- Beim Einstieg in Fintech können Vocabulary und Konzepte schwieriger sein als der Code, daher werden die wichtigsten Begriffe separat zusammengefasst.
- Im Bereich Accounting und Ledger gehören dazu ledger, general ledger und sub-ledger, debit/credit, posting, chart of accounts, receivable/payable, IOU, accrual vs cash basis, trial balance, suspense/clearing account, write-off, commingling und reconciliation break.
- Im Bereich Money und FX gehören dazu Money type, minor units, basis point, notional, fiat vs crypto, stablecoin, pegged/wrapped/bridged, bid/ask/spread, mid-market rate, reference rate und mark-to-market.
- Im Bereich Transactions und Settlement gehören dazu value date, booking date, settlement date, T+X, clearing vs settlement, cut-off time, float, netting, backdating und reversal/correction.
- Auch Begriffe aus Payments, Karten, Märkten, Crypto und Compliance werden separat zusammengefasst.
- PSP, omnibus account, FBO account, chargeback, issuer/acquirer, authorization vs capture
- order book, market vs limit order, maker/taker, slippage, liquidity, derivative, futures, perpetual, liquidation
- custody, hot/cold wallet, private key, multisig, MPC, gas, confirmation/finality, reorg, UTXO vs account model
- KYC, AML/CFT, sanctions screening, PEP, SoF/SoW, Travel Rule, VASP, MiCA, least privilege, RBAC, audit trail
- Die Referenzmaterialien sind in Accounting und Ledger, Payments und Karten, Märkte und Trading, Crypto, Engineering sowie KYC und AML unterteilt.
- Accounting for Computer Scientists: Ein Text für Engineers, der doppelte Buchführung mit Graphen und Datenmodellen erklärt
- Modern Treasury, How to Scale a Ledger: Eine Artikelserie, die Production Ledgers aus der Perspektive von Software Engineering behandelt
- Designing Data-Intensive Applications: Behandelt Idempotenz, Log, Konsistenz und Failure Modes aus Systemperspektive
Drei End-to-End-Beispiele
-
Crypto-Auszahlung
- Dies ist der Ablauf, bei dem ein Nutzer 0,5 ETH an eine externe Adresse auszahlt.
- Die Anfrage enthält einen Idempotency Key, sodass wiederholtes Absenden nur eine einzige Auszahlung erzeugt.
- 0,5 ETH und die erwartete Network Fee werden im verfügbaren Guthaben reserviert.
- Das Compliance-Gate prüft Sanktionen, AML und die Zieladresse und kann wegen externer Aufrufe und manueller Prüfung mehrere Tage lang im Sleep-Zustand bleiben.
- Das On-Chain-Broadcasting muss idempotent sein; nach einem Crash darf nicht ein zweites Mal gesendet werden, sondern die Chain muss erneut geprüft werden.
- Nach ausreichenden Confirmations werden im Ledger ein Debit auf dem User Account, ein Credit auf dem externen On-Chain-Account, die Network-Fee-Kosten und die Service-Fee-Umsätze verbucht.
- Ein nächtlicher Job gleicht Ledger und die Realität auf der Chain ab.
-
Card Deposit
- Dies ist der Ablauf für eine Kartenaufladung über einen PSP.
- Der Nutzer übermittelt Betrag und Kartendaten und eröffnet beim PSP mit einem Idempotency Key eine Deposit-Transaktion.
- Die Authorization erzeugt nur einen Hold; da das Geld dem Unternehmen noch nicht gehört, wird das User Balance noch nicht gutgeschrieben.
- Der
captured-Webhook validiert die Signatur der Raw Bytes, speichert die Raw Payload, antwortet schnell mit 2xx und verarbeitet dann asynchron. - Der Webhook ist nur ein Trigger, daher wird der maßgebliche Zustand über die PSP-API abgefragt.
- Der Status captured but not settled wird über ein Clearing Account verbucht; das Settlement kann gebündelt erst nach T+X eintreffen.
- Ein Chargeback wird nicht durch Änderung des Originals verarbeitet, sondern über einen verknüpften kompensierenden Eintrag.
-
In-App-Konvertierung mit Cashback
- Dies ist der Ablauf, bei dem 1.000 EUR in USDC umgetauscht werden und ein Promotional Cashback gewährt wird.
- Ein EUR→USDC-Quote ist nicht der Kehrwert von USDC→EUR, sondern ein richtungsabhängiger Kurs.
- EUR und USDC werden nicht miteinander addiert; USDC wird durch
(network, contract address)identifiziert und ist nicht dasselbe wie an Fiat gekoppelte Werte. - Berechnungen behalten die volle Präzision bei und runden nur einmal an der Grenze mit einer expliziten Strategie.
- Der Spread muss explizit auf ein Revenue Account gebucht werden und darf nicht als Rounding Residual verschwinden.
- Cashback ist keine kostenlose Erhöhung des Guthabens, sondern echtes Geld, das vom Promotional-/Expense-Account des Unternehmens auf das User Balance verschoben wird.
- Die Veröffentlichung der Ergebnisse stellt mit Mechanismen wie Outbox, CDC oder Event Log eine zuverlässige Zustellung sicher, und nachgelagerte Consumer deduplizieren anhand einer stabilen Event-ID.
1 Kommentare
Hacker-News-Kommentare
Ich habe es überflogen, und dieses Handbuch wirkt oberflächlich; in manchen Bereichen kommt es schlechtem Rat nahe.
Wenn ich zum Beispiel sehe, dass Geldbeträge in einer nicht-ganzzahligen Form gespeichert werden, würde ich wohl schreiend weglaufen. Etwa wegen Fällen, in denen Rusts
decimalals JSON-Gleitkommazahl dargestellt wird. Solange es keinen sehr starken Grund dagegen gibt, sollten Beträge immer ganzzahlig sein; die exportierte View kann dann jedes beliebige, auch seltsam bitcodierte Format haben.Auch Wechselkurse sind kein Problem, das sich mit einem einzelnen Zeitpunkt erledigt. Der Kurs zum Käuferzeitpunkt, der Kurs zum Verkäuferzeitpunkt, Einigung, Toleranzen bei der Einigung und ein vereinbarter finaler Zeitstempel spielen alle eine Rolle.
Wegen der Unveränderlichkeit möchte man überall dort, wo mit Geld gearbeitet wird, Event Sourcing einsetzen. Der endgültig bereinigte Stream sieht vielleicht aus wie
A -> B -> E, der tatsächliche Stream kann aberA0 -> Edit(A0, A) -> B -> C -> D -> Rollback(B) -> Esein.Letztlich ist Fintech eben nicht gleich Fintech. An manchen Orten wurde Geld wie Stückgut behandelt, an anderen stand Geld im Zentrum von allem.
Auch bei Devisen scheint das eher die Aussage des Handbuchs zu stützen, dass es „keinen kanonischen Kurs“ gibt. Außerdem behandelt der Text Aufzeichnungen nach der Finalisierung, während es hier wohl um die Methode der Finalisierung geht. Das ist eine gültige Nuance für ein anderes Ziel, wirkt aber nicht wie ein Beleg dafür, dass etwas fehlt oder falsch ist.
Auch beim Thema Unveränderlichkeit scheint der Text dasselbe zu sagen. Ich sehe nicht, worin der Unterschied liegen soll.
Wenn man Monte-Carlo-Optionspreise auf Zinskurven berechnet und sich für Risikokennzahlen wie Duration, Konvexität oder Vega interessiert, kümmert sich niemand darum, welche Rundungsregeln gelten. double reicht aus. Wie willst du
exp(-rt)cashflowoder die kumulative Normalverteilungsfunktion auf Integer zwingen?Es gibt Bereiche, in denen Integer richtig sind. Aber das ist kein allgemeingültiges Prinzip; man sollte die passende Engineering-Entscheidung treffen.
Wenn die verwendete Umgebung es unterstützt, kann man auch Festkomma verwenden, aber technisch ist das immer noch ein Integer.
Für die Anzeige ist es sicher, Decimal-Werte zurückzugeben.
Schön, dass du vor einem System wegläufst, das Geldbeträge als Integer speichert. Dann arbeiten wir vermutlich nicht im selben System. Heutzutage möchte ich eher oft vor Systemen, die Geldbeträge als Integer behandeln, weglaufen. In einer idealen Codebasis, die nur von erfahrenen Finanzprogrammierern angefasst wird, kann das gut funktionieren; solche Systeme laufen aber meist Gefahr, übermäßig exklusiv oder fragil zu werden.
Wer erwägt, Beträge mit einer Strategie der minor-unit precision darzustellen: Mein Rat wäre, es lieber nicht zu tun. Zumindest sollte man es nicht als Austausch-/API-Datenformat verwenden.
Es wirkt clever – schnelle Integer-Operationen, keine Rundungsprobleme bei Addition und Subtraktion –, aber sobald man mit Partnern arbeitet, die für eine bestimmte Währung implizit eine andere Anzahl von Stellen annehmen, kann einen das heftig treffen. Besonders wichtig ist das bei Stablecoins, weil sie oft eine andere implizite Zahl an Dezimalstellen haben als die Fiatwährung, die sie repräsentieren.
Bei JSON-basierten APIs kann es sich auch lohnen, Beträge als String-Typ darzustellen. JSON legt keine Dezimalpräzision fest, daher muss man immer sicherstellen, dass weder man selbst noch alle Nutzer/Vendoren durch Parser/Serializer intern über Floating Point gehen und dabei Präzision verlieren. Das kann schnell unschön werden, und Strings wirken konzeptionell weniger sauber, umgehen dieses Problem aber vollständig. Manche würden das ein Anti-Pattern [1] nennen, aber ich möchte diesen Kampf um ideologische Reinheit nicht auf den Schultern von Nutzern oder Aktionären austragen.
[1] https://blog.json-everything.net/posts/numbers-are-numbers-n...
Im High-Frequency-Trading kann man Übertragungsplatz sparen, wenn man für einen bestimmten {slice} vorab einen konsistenten Exponenten festlegen kann. Zum Beispiel innerhalb eines Bereichs wie Produkt/Tick-Size/Assetklasse/Börse/Feed/Server: Man sendet nur die Mantisse, und der Client verwendet einen hartcodierten Exponenten.
Aber selbst in ähnlichen Bereichen lohnt es sich oft, in den übertragenen Daten zusätzlich einen
uint32-Exponenten mitzuschicken. So kann man ihn später ändern und wird nicht durch ein frühes Design wie „im Moment brauchen wir nur Cents“ ausgebremst. Zum Beispiel könnte man plötzlich Bitcoin-Preise mit voller Präzision unterstützen müssen. Nutzer werden dankbar sein, wenn man keine Breaking Change koordinieren muss, nur weil man den festen Exponenten anpassen will.Der Maßstab „beliebig“ ist unvernünftig. Das ist ein unerreichbarer Standard, der unbegrenztes Engineering-Budget verlangen kann, ohne tatsächlich nachweisbaren Wert zu liefern.
Es ist gut, das Fehlen eines Standards zu benennen, darüber zu sprechen, wie reale Parser funktionieren, und Lücken sowie unerfüllte Use Cases zu diskutieren. Es ist auch gut vorzuschlagen, dass ein vernünftigerer Standard nötig ist. Aber zu verlangen, dass alle „alle Möglichkeiten“ unterstützen, die niemand tatsächlich braucht, deren Bedeutung unklar ist und die praktisch auch nicht erreichbar sind, ist keine gute Idee.
float/double, Fixkommaarithmetik in Tausendsteln einer Minor Unit oder kleineren Einheiten, Dezimalzahlen mit beliebiger Präzision oder etwas völlig anderes?Wenn ich als Programmierer sehe, wie Fintech-Programmierer jeweils aus unterschiedlichen Erfahrungen und Perspektiven sprechen, frage ich mich, was es eigentlich heißt, gut programmieren zu können.
Dass xlii sagt, man solle Geldbeträge nicht als Floating Point speichern, ist das übliche IEEE-754-Problem. Finanz-Tracking sollte über unveränderliche Logs oder eventbasierte Aufzeichnungen laufen, aber ich denke nicht, dass man alle umgebenden Services per Event Sourcing bauen muss. Es reicht meiner Meinung nach, das nur auf Kernlogik wie Ledger, Settlement, Orders und Executions anzuwenden. Wenn man xliis Text liest, wirkt es wie eine Technik, die erst dann realisierbar ist, wenn das Modeling gelungen ist.
lxgrs Aussage spricht das Minor-Unit-Problem an. Wenn JSON-Zahlen von Sprache oder Parser als Floating Point geparst werden, kann Präzision verloren gehen. Üblicherweise sendet man den Wert zusammen mit einem separaten Feld für die Anzahl der Dezimalstellen. Allerdings habe ich gehört, dass man das im High-Frequency-Trading nicht macht, weil schon dieser Overhead zu teuer ist.
Antonymooses Aussage deckt sich mit dem, was viele Bücher sagen. Deshalb ist ein solches Design im Kontext von Devisenhandel oder APIs verbreitet. Es fühlt sich auch ein wenig wie Protokolldesign an.
Zusammengenommen haben alle innerhalb ihrer eigenen Domäne recht. Ich fände es gut, wenn jemand wie xlii ein Senior Programmer wäre, aber gleichzeitig glaube ich nicht, dass ich ein so komplexes System entwerfen könnte. In diesem Sinne sind die jeweiligen Aussagen plausibel, und es ist interessant zu sehen, wie die Meinungen je nach Domäne auseinandergehen. Vielleicht ist genau das Expertise.
Wenn man so etwas sieht, kann man ungefähr ableiten, aus welcher Erfahrung ein Programmierer kommt. Manchmal fühlt sich Programmieren nicht wie die Suche nach der richtigen Antwort an, sondern wie die Wahl einer Weltanschauung.
Auf HN zu sehen, wie Programmierer ihre jeweilige Domäne modellieren, ist immer interessant. Manchmal klicke ich auf Profile und füge ihr Domänenwissen meinem persönlichen Wiki hinzu, in der Annahme, dass ich es irgendwann vielleicht einmal brauche.
Gut. Dieses Buch enthält viele gute Informationen, die man auch anderswo finden kann, aber allein die Zusammenstellung ist schon ziemlich praktisch. Kleppmanns Designing Data-Intensive Applications kann ich sehr empfehlen. Die erste Auflage war schon sehr gut, und kürzlich ist die zweite erschienen.
Ich habe als CTO in einem FinTech gearbeitet und dort den gesamten Software-Stack von Grund auf aufgebaut; die Lektionen des Buchs stimmen im Großen und Ganzen. „Im Großen und Ganzen“, weil man wie immer bei konkreten Projekten viel „kommt darauf an“ berücksichtigen muss. Zum Beispiel habe ich kein Event Sourcing verwendet, um das Problem zu vermeiden, den gesamten Zustand berechnen zu müssen. Ein standardmäßiger Append-only-Audit-Trail kann völlig ausreichen.
Exactly-once Delivery kann man nicht garantieren, aber man kann faktisch einmalige Verarbeitung aufbauen, und das ist in der Praxis auch das, was man will.
Die Empfehlung, alle Requests und Responses zu speichern, ist absolut richtig. Das gilt nicht nur beim Konsumieren von APIs, sondern auch immer dann, wenn man Informationen aus der Außenwelt sammelt; wenn möglich, sollte man innerhalb der eigenen Grenzen auch alle Zwischenschritte der Transformation aufzeichnen. Eine Kombination aus content-adressierten Buckets und relationalen Tabellen ist gut.
Außerdem sagt der Text nichts über Data Lineage. Was macht man, wenn ein Vendor mitten am Tag irgendwelche Daten aktualisiert hat und man das unbedingt wissen muss? Man muss in der Lage sein, Berechnungen, die die alten Werte verwendet haben, erneut auszuführen und dasselbe Ergebnis zu erhalten, und gleichzeitig diese Änderung erklären können. Das ist kein besonders schwer zu lösendes Problem, aber es erfordert Nachdenken.
„Schlechter Rat“ ist noch ziemlich höflich formuliert. Ehrlich gesagt wirkt dieses „Handbuch“ größtenteils, als wäre es von einem LLM geschrieben worden.
Im Abschnitt zur Unveränderlichkeit steht zum Beispiel dieser Satz: „Wenn man PII von Finanzdaten trennt, kann man das Recht auf Löschung respektieren, ohne die aufzubewahrende Finanzhistorie zu verlieren.“
Bei Finanzinstituten gehören diese Daten aus offensichtlichen KYC-/AML-Gründen zusammen.
Wenn man Kundennamen, Adressen usw. auf Anfrage sofort löscht, bevor die relevanten Fristen abgelaufen sind, und nur die Finanzdaten behält, dann hat die ganze Organisation einen sehr schlechten Tag, wenn eine rechtmäßige Behörde die Daten zur Verfolgung von Straftaten anfordert.
Wer im Fintech arbeiten will, sollte sich nicht auf irgendein beliebiges „Handbuch“ verlassen, das von einer unbekannten Person in einer unbekannten Jurisdiktion geschrieben wurde.
Wer im Fintech arbeitet, sollte ausschließlich nach den internen Handbüchern/Richtlinien usw. des Arbeitgebers arbeiten. Solche Dokumente wurden vermutlich gemeinsam von den Anwälten und Compliance-Verantwortlichen des Unternehmens erstellt, damit sie den Gesetzen und Meldepflichten der Jurisdiktionen entsprechen, in denen der Arbeitgeber tätig ist.
Für mich empfiehlt er letztlich, PII, die gelöscht werden muss, von Daten zu trennen, die in Buchhaltungsgleichungen/Invarianten eingehen und die man daher praktisch dauerhaft aufbewahren möchte. So kann man Erstere löschen, nachdem die entsprechenden Aufbewahrungsfristen abgelaufen sind.
Es stimmt, dass man sich nicht auf ein „Handbuch“ verlassen sollte, das von einer unbekannten Person in einer unbekannten Jurisdiktion geschrieben wurde. Man sollte die darin enthaltenen Ideen und Praktiken aber auch nicht blind ignorieren oder nie über die eigene Organisation hinausschauen. Idealerweise sieht man sie sich an und gleicht sie mit dem eigenen Wissen und den lokalen Vorschriften ab.
In einer Welt, in der es nur perfekte, fehlerfreie Organisationen gibt, wirkt der Ansatz, nur die internen Vorgaben des Arbeitgebers zu befolgen, vernünftig. Aber wie soll man ohne solche Gespräche überhaupt auf dieses Niveau kommen?
Ich finde, der Großteil davon gilt nicht nur für Fintech, sondern für Software Engineering insgesamt.
Die Abschnitte zu Retries, Idempotenz, Event-Reihenfolge usw. gelten zum Beispiel für jedes System, das ein gewisses Maß an Korrektheit braucht, auch wenn es nicht direkt um Geld geht. Ich habe zu viele Systeme gesehen, die mit der Annahme gebaut wurden: „Man kann es jederzeit erneut versuchen.“ Aber Retries sind nur möglich, wenn der Fehler überhaupt sauber auftritt, und die nachgelagerten Systeme müssen das Maß an Idempotenz bieten, das ich annehme. Das wird in der Praxis oft nicht wirklich überprüft.
Ich würde lieber einen Text lesen, der radikalere Ansätze wie eine Datenbank pro Konto vertritt – Dinge mit echten, Fintech-spezifischen Trade-offs.
Der wichtigste Rat, den ich Fintech-Engineers oder Gründern geben würde, ist: Nehmt Risiko und Compliance vom ersten Tag an ernst.
Finanzsysteme basieren auf Vertrauen. Wenn man Risiken nicht nachweisbar mindern kann, verliert man Vertrauen – und am Ende das ganze Geschäft.
Die Aussage, man solle Gleitkommazahlen vermeiden, stimmt nicht. Ich habe 20 Jahre im Fintech gearbeitet, und meistens haben wir double verwendet. Excel verwendet ebenfalls double, Frontends werden double verwenden, und jede Datenbank unterstützt double. Standardbibliotheken können double parsen, und JSON verwendet in der Praxis double, auch wenn die Theorie etwas anderes sagt. Viele ERP-Systeme verwenden ebenfalls double.
Der entscheidende Punkt beim Umgang mit Währungen in double ist, im Kopf zu behalten, dass man insgesamt 15 Stellen Genauigkeit unterbringen kann. Solange Zahlen nicht mehr Stellen als etwa
123456789.01oder123.456789verwenden, kann man bei Finanzberechnungen perfekte dezimale Genauigkeit haben. Man muss nur nach jeder Berechnung und vor jedem Vergleich das Ergebnis immer auf eine Genauigkeit innerhalb dieser 15 Stellen runden. Excel macht das so.Der größte Vorteil von double ist die breite Unterstützung und die Möglichkeit, innerhalb eines Systems verschiedene Genauigkeiten zu mischen. Bei internationaler Finanzwelt oder komplexeren Finanzprodukten kommt das vor. Manche Buchhaltung braucht Genauigkeit bis auf Tausendstel, anderes muss auf Vielfache von 0,25 gerundet werden. Am Ende verwendet man ohnehin keine einfache Arithmetik, sondern eine spezialisierte Bibliothek für Buchhaltungsmathematik, und diese Bibliothek kann Gleitkommazahlen als Backend vollkommen problemlos verwenden.
Eine Plaid-Saldoabfrage garantiert nicht, dass eine demnächst eingereichte ACH-Lastschrift erfolgreich sein wird.
Es ist egal, ob der Kontostand eine Million Dollar beträgt. Bevor ACH verarbeitet wird, kann das ganze Geld (a) per Wire Transfer abgeflossen sein, (b) durch ACHs von gestern – also Rechnungen, Lastschriften usw. – sowie Schecks verrechnet worden sein oder (c) per Debitkarte/ATM ausgegeben worden sein.
Wie ich weiß, dass manche Fintechs das nicht berücksichtigen, sage ich wohl lieber nicht.
Allein der Abschnitt zu Idempotency Keys ist die Lektüre wert. Die meisten Entwickler lernen diese Lektion auf die harte Tour.
Viele davon stammen aus einer Zeit, bevor Wissen über Idempotenz weit verbreitet war. Deshalb werden oft mehrere Felder, die global eindeutig wirken, aneinandergereiht, um notdürftig einen Idempotency Key zu bauen. Das Problem ist: Er ist nie wirklich vollständig eindeutig. Manchmal kann man hinter den Vorhang schauen, etwa wenn eine Bank verhindert, dass am selben Tag derselbe Betrag auf dasselbe Empfängerkonto überwiesen wird.
Ich habe viel Zeit damit verbracht zu erklären, wie Idempotenz funktionieren sollte und warum sie wichtig ist. Die meisten Teams verstehen die Notwendigkeit, aber nur wenige haben von Anfang an daran gedacht.
„Fintech“ ist sehr breit, und vieles von dem, was Fintech genannt wird, ist eigentlich Kommunikation: Kommunikation zwischen Unternehmen, zwischen Tradern, zwischen Systemen und zwischen Ledgers. Es gibt keine in der gesamten Branche gültige „richtige“ Art zu programmieren. Am Ende ist die richtige Art nämlich diejenige, die der Kommunikationspartner verstehen kann.
Wenn die Gegenseite Währungen in Cent verfolgt, führt eine höhere Genauigkeit auf der eigenen Seite zu Rundungsabweichungen. Umgekehrt gilt dasselbe, wenn ich in Cent rechne und die Gegenseite mit Zehntelcent arbeitet. Alle anderen Ratschläge in diesem Dokument sollte man genauso betrachten.