Voordat we ons in de chaos storten, laten we onszelf eraan herinneren waarom we in de eerste plaats voor event sourcing vielen:

  • Volledig auditspoor? Check.
  • Mogelijkheid om eerdere toestanden te reconstrueren? Check.
  • Flexibiliteit om ons domeinmodel te ontwikkelen? Check.
  • Schaalbaarheid en prestatievoordelen? Dubbelcheck.

Het klonk allemaal te mooi om waar te zijn. Spoiler alert: dat was het ook.

De Setup: Ons Voorraadbeheersysteem

Ons systeem was ontworpen om miljoenen SKU's in meerdere magazijnen te beheren. We kozen voor event sourcing om een nauwkeurige geschiedenis van elke voorraadbeweging, prijswijziging en itemattribuutupdate bij te houden. De event store was onze bron van waarheid, met projecties die de huidige staat voor snelle queries boden.

Hier is een vereenvoudigde versie van onze eventstructuur:

{
  "eventId": "e123456-7890-abcd-ef12-34567890abcd",
  "eventType": "StockAdded",
  "aggregateId": "SKU123456",
  "timestamp": "2023-04-01T12:00:00Z",
  "data": {
    "quantity": 100,
    "warehouseId": "WH001"
  },
  "version": 1
}

Ziet er onschuldig uit, toch? Oh, hoe naïef waren we.

De Ontknoping: Valkuilen van Event Versioning

Onze eerste grote tegenslag kwam toen we ons StockAdded event moesten bijwerken om een reason veld toe te voegen. Simpel genoeg, dachten we. We verhogen gewoon de versie en voegen een migratiestrategie toe. Wat kan er misgaan?

Alles. Alles kon misgaan.

Les 1: Versieer Je Events Alsof Je Leven Ervan Afhangt

We maakten de klassieke fout om één versienummer voor alle events te gebruiken. Dit betekende dat toen we StockAdded bijwerkten, we per ongeluk de verwerking van alle andere events braken.

Hier is wat we hadden moeten doen:

{
  "eventType": "StockAdded",
  "eventVersion": 2,
  "data": {
    "quantity": 100,
    "warehouseId": "WH001",
    "reason": "Initial stock"
  }
}

Door elk type event onafhankelijk te versien, hadden we het domino-effect kunnen vermijden dat ons systeem op de knieën bracht.

Les 2: Migraties Zijn Niet Optioneel

We dachten aanvankelijk dat we weg konden komen met het afhandelen van beide versies in onze event handlers. Grote fout. Naarmate het systeem groeide, werd deze aanpak onhoudbaar.

In plaats daarvan hadden we een robuuste migratiestrategie moeten implementeren:


def migrate_stock_added_v1_to_v2(event):
    if event['eventVersion'] == 1:
        event['data']['reason'] = 'Legacy import'
        event['eventVersion'] = 2
    return event

# Pas migraties toe bij het lezen van de event store
events = [migrate_stock_added_v1_to_v2(e) for e in read_events()]

De Snapshot Saga: Wanneer Optimalisaties Terugslaan

Naarmate onze event store groeide, werd het herbouwen van projecties pijnlijk traag. Enter snapshots: onze veronderstelde redder die in een nieuwe nachtmerrie veranderde.

Les 3: Snapshotfrequentie Is een Delicaat Evenwicht

We maakten aanvankelijk elke 100 events snapshots. Dit werkte prima totdat we een plotselinge piek in transacties kregen, waardoor onze snapshotcreatie achterbleef en onze projecties steeds meer verouderden.

De oplossing? Adaptieve snapshotfrequentie:


def should_create_snapshot(aggregate):
    time_since_last_snapshot = current_time() - aggregate.last_snapshot_time
    events_since_last_snapshot = aggregate.event_count - aggregate.last_snapshot_event_count
    
    return (time_since_last_snapshot > MAX_TIME_BETWEEN_SNAPSHOTS or
            events_since_last_snapshot > MAX_EVENTS_BETWEEN_SNAPSHOTS)

Les 4: Snapshots Hebben Ook Versies Nodig

We vergaten onze snapshots te versien. Toen we onze aggregaatstructuur veranderden, brak de hel los. Oudere snapshots werden incompatibel en we konden onze projecties niet herbouwen.

De oplossing? Versieer je snapshots en bied upgradepaden:


def upgrade_snapshot(snapshot):
    if snapshot['version'] == 1:
        snapshot['data']['newField'] = calculate_new_field(snapshot['data'])
        snapshot['version'] = 2
    return snapshot

# Gebruik bij het laden van snapshots
snapshot = upgrade_snapshot(load_snapshot(aggregate_id))

Het Corruptie Raadsel: Wanneer Je Bron van Waarheid Liegt

De laatste nagel aan onze doodskist was corruptie in de event store. Een perfecte storm van netwerkproblemen, een bug in onze event store en wat te agressieve foutafhandeling leidden tot dubbele en ontbrekende events.

Les 5: Vertrouw, maar Verifieer

We vertrouwden blindelings op onze event store. In plaats daarvan hadden we checksums en periodieke integriteitscontroles moeten implementeren:


def verify_event_integrity(event):
    expected_hash = calculate_hash(event['data'])
    return event['hash'] == expected_hash

def perform_integrity_check():
    for event in read_all_events():
        if not verify_event_integrity(event):
            raise IntegrityError(f"Corrupt event detected: {event['eventId']}")

Les 6: Implementeer een Herstelstrategie

Wanneer corruptie optreedt (en dat zal het), heb je een manier nodig om te herstellen. Wij hadden er geen, en dat kostte ons veel. Hier is wat we hadden moeten doen:

  1. Onderhoud een aparte, alleen-aanvullende log van alle inkomende commando's.
  2. Implementeer een verzoeningsproces om de commando-log met de event store te vergelijken.
  3. Creëer een herstelproces om ontbrekende events opnieuw af te spelen of duplicaten te verwijderen.

def reconcile_events():
    command_log = read_command_log()
    event_store = read_event_store()
    
    for command in command_log:
        if not event_exists_for_command(command, event_store):
            replay_command(command)
    
    for event in event_store:
        if is_duplicate_event(event, event_store):
            remove_duplicate_event(event)

De Feniks Herrijst: Heropbouwen met Veerkracht

Na talloze slapeloze nachten en meer koffie dan ik wil toegeven, stabiliseerden we eindelijk ons systeem. Hier zijn de belangrijkste lessen die ons hielpen uit de as te herrijzen:

  • Event versioning is niet optioneel – doe het vanaf dag één.
  • Implementeer robuuste migratiestrategieën voor zowel events als snapshots.
  • Adaptieve snapshotcreatie balanceert prestaties en consistentie.
  • Vertrouw niets – implementeer integriteitscontroles op elk niveau.
  • Heb een duidelijke herstelstrategie voordat je deze nodig hebt.
  • Uitgebreid testen, inclusief chaos engineering, kan je redden.

Conclusie: Het Tweesnijdend Zwaard van Event Sourcing

Event sourcing is krachtig, maar ook complex. Het is geen wondermiddel en vereist zorgvuldige overweging en robuuste engineeringpraktijken om succesvol te zijn in productie.

Onthoud, met grote kracht komt grote verantwoordelijkheid – en in het geval van event sourcing, veel slapeloze nachten. Maar gewapend met deze lessen ben je nu beter voorbereid om de uitdagingen van event sourcing in de praktijk aan te pakken.

Nu, als je me wilt excuseren, ik heb wat PTSD om door te werken. Kent iemand een goede therapeut die gespecialiseerd is in event sourcing trauma?

"In event sourcing, net als in het leven, gaat het niet om het vermijden van mislukkingen – het gaat om gracieus falen en sterker herstellen."

Verder Lezen

Heb je je eigen demonen van event sourcing bevochten? Deel je oorlogsverhalen in de reacties – ellende houdt immers van gezelschap!