Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Event-Sourcing & CQRS - Eine Einführung

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →

Event-Sourcing & CQRS - Eine Einführung

Avatar for Christian

Christian

August 03, 2018

Other Decks in Programming

Transcript

  1. Christian Hammerl mantiz dermantiz chammerl Software-Entwickler seit 18 Jahren Schwerpunkte:

    PHP, C++ seit 7 Jahren angestellt bei der Crosscan GmbH in Witten Interessen: Software-Entwicklung, DDD, Tooling & Automation, DX, ...
  2. Warenkorb eines Online-Shops id: Interne Datenbank-ID (auto-increment) basketId: eindeutiger Identi

    er (z.B. Session-ID) productId: Produkt-Referenz amount: Anzahl ...
  3. Ein Blick in die Datenbank: id basket_id product_id amount ...

    ... ... ... ... ... 123 aaaaaaaa 42 5 ... 154 aaaaaaaa 23 2 ... 200 bbbbbbbb 23 2 ... ... ... ... ... ... Welche Probleme könnte es mit diesem Modell geben?
  4. Klar: Produktänderungen: Name, Preis, ... Nicht so offensichtlich: wie ist

    es zu diesem Warenkorb gekommen? wurden nur diese beiden Produkte in den Warenkorb gepackt? wurden evtl. auch andere Produkte ergänzt, aber wieder entfernt? wurde die Anzahl zwischenzeitlich geändert?
  5. basket.addProduct(42, 5); basket.addProduct(23, 2); versus: basket.addProduct(42, 5); basket.addProduct(17, 1); basket.addProduct(23,

    2); basket.removeProduct(17); versus: basket.addProduct(42, 1); basket.changeAmount(42, 10); basket.addProduct(23, 2); basket.changeAmount(42, 5);
  6. Business: Entwickler: Gibt es ein Problem mit diesem Ansatz? Könnt

    ihr mir eine Statistik über Produkte erstellen, die einmal im Warenkorb eines Benutzers waren, dann aber von diesem Benutzer nie bestellt wurden? “ “ Klar, dafür können wir eine Liste von entfernten Produkten p egen. Wird ein Produkt dann später bestellt, kann es aus dieser Liste entfernt werden. “ “
  7. Business: Entwickler: Gibt es ein Problem mit diesem Ansatz? Könnt

    ihr mir eine Statistik über Produkte erstellen, die aus dem Warenkorb entfernt, dann aber zu einem späteren Zeitpunkt bestellt wurden? “ “ Klar, die Liste der entfernten Produkte kann dahingehend erweitert werden, dass das Datum der Bestellung ergänzt wird, anstatt das später bestellte Produkt aus der Liste zu entfernen. “ “
  8. Zur Erinnerung ein Blick in die Datenbank: id basket_id product_id

    amount ... ... ... ... ... ... 123 aaaaaaaa 42 5 ... 154 aaaaaaaa 23 2 ... 200 bbbbbbbb 23 2 ... ... ... ... ... ...
  9. Statt des aktuellen States speichern wir alle Events, die zum

    aktuellen State geführt haben: id streamId version type payload 100 aaaaaaaa 1 BasketCreated {} 101 aaaaaaaa 2 ProductAdded {"productId": "42", "amount": "1"} 102 aaaaaaaa 3 AmountChanged {"productId": "42", "newAmount": "5", "previousAmount": "1"} 103 aaaaaaaa 4 ProductAdded {"productId": "17", "amount": "1"} 104 aaaaaaaa 5 ProductAdded {"productId": "23", "amount": "2"} 105 aaaaaaaa 6 ProductRemoved {"productId": "17"} ... ... ... ... ... 203 bbbbbbbb 1 BasketCreated {} 204 bbbbbbbb 2 ProductAdded {"productId": "23", "amount": "2"} ... ... ... ... ... evtl. weitere Felder: Datum des Events, Metadaten
  10. class Basket { string id; Map<int, int> products = new

    Map<int, int>(); public static Basket create(string id) { Basket basket = new Basket(); basket.apply(new BasketCreated(id)); return basket; } protected void apply(BasketCreated event) { id = event.id; } }
  11. class Basket { public void addProduct(int productId, int amount) {

    if (products.containsKey(productId)) { int previousAmount = products.get(productId); int newAmount = previousAmount + amount; apply(new AmountChanged(productId, newAmount, previousAmount)); } else { apply(new ProductAdded(productId, amount)); } } protected void apply(ProductAdded event) { products.put(event.productId, event.amount); } }
  12. class Basket { public void changeAmount(int productId, int newAmount) {

    if (newAmount == 0) { apply(new ProductRemoved(productId)); } else { if (!products.containsKey(productId)) { apply(new ProductAdded(productId, newAmount)); } if (products.get(productKey) != newAmount) { apply(new AmountChanged(productId, newAmount, amount)); } } } protected void apply(ProductAdded event) { products.put(event.productId, event.amount); } }
  13. Object load(string streamId) : lädt alle Events in korrekter Reihenfolge

    stellt aktuellen State durch Aufruf entsprechender apply Methoden wieder her void save(Object object) : holt sich eine Liste aller neuen Events speichert diese in der Reihenfolge ihres Auftretens
  14. CQS Command-Query-Separation by Bertrand Meyer für Eiffel Command: ändert State

    ohne Return Value Query: liest State ohne jegliche Seiteneffekte Separation: eine Methode ist entweder ein Command oder ein Query - aber nicht beides
  15. lose Kopplung zwischen Controller/CLI und Service-Layer bzw. CommandHandler einfache Möglichkeit

    Commands asynchron abzuarbeiten sehr schnelle Response-Zeiten im asynchronen Fall aber keine Möglichkeit Fehler abzufangen und Benutzer anzuzeigen
  16. 2 Sources of truth: Write-Model-Datenbank Events Was passiert wenn der

    State nach dem Emitten eines Events nicht persistiert werden kann? Was passiert wenn wir ein Event "verlieren"?
  17. Write-Model: nutzt Event-Sourcing nur persistierte Events werden an Event-Bus dispatcht

    Ist das nicht wahnsinning inperformant? → glücklicherweise weit weniger als man denkt Ein Event-Store arbeitet append-only: → Events können beliebig gecacht werden
  18. Read-Model: wird über Event-Handler aufgebaut spezialisierte Modelle für verschiedene Use-Cases

    → RDBMS, NoSQL, Dateisystem, InMemory Projektion aller Events des Write-Models → kann "jederzeit" neu generiert werden
  19. Verschiedene Arten Event-Handler: Projektoren: bauen das Read-Model können für Replays

    verwendet werden Prozessoren: z.B. E-Mail senden auf keinen Fall für Replays verwenden Sagas/Prozess-Manager langlaufende Prozesse, die mehr als ein Command benötigen und/oder mehrere Event-Streams betreffen auf keinen Fall für Replays verwenden
  20. Business: Entwickler: Könnt ihr mir eine Statistik über Produkte erstellen,

    die einmal im Warenkorb eines Benutzers waren, dann aber von diesem Benutzer nie bestellt wurden? “ “ Klar, wir brauchen nur eine neue Projektion und müssen über alle relevanten Events iterieren. “ “
  21. Business: Entwickler: Könnt ihr mir eine Statistik über Produkte erstellen,

    die aus dem Warenkorb entfernt, dann aber zu einem späteren Zeitpunkt bestellt wurden? “ “ Klar, wir brauchen nur eine neue Projektion und müssen über alle relevanten Events iterieren. “ “
  22. Downsides gewöhnungsbedürftig lange Event-Streams (> 1000 Events) → Snapshotting (s.

    Buchhaltung Jahresabschluss) Bulk-Operationen → Read-Model abfragen → Command je Event-Stream "Events sind immutable" → Korrektur-Events (s. Buchhaltung) Eventual Consistency → häu g kein echtes Problem