Gestione della concorrenza ES / CQRS

14

Recentemente ho iniziato a immergermi in CQRS / ES perché potrei aver bisogno di applicarlo al lavoro. Sembra molto promettente nel nostro caso, in quanto risolverebbe molti problemi.

Ho abbozzato la mia comprensione approssimativa su come un'app ES / CQRS dovrebbe apparire contestualizzata a un caso di utilizzo bancario semplificato (prelevare denaro).

Per riassumere, se la persona A ritira dei soldi:

  • viene emesso un comando
  • Il comando
  • viene consegnato per la convalida / verifica
  • un evento viene trasferito in un archivio eventi se la convalida ha esito positivo
  • un aggregatore deseleziona l'evento per applicare modifiche all'aggregato

Da quello che ho capito, il registro eventi è la fonte della verità, poiché è il registro dei FATTI, quindi possiamo ricavarne qualsiasi proiezione.

Ora, ciò che non capisco, in questo grande schema di cose, è ciò che accade in questo caso:

  • regola: un saldo non può essere negativo
  • la persona A ha un saldo di 100e
  • persona A emette un WithdrawCommand di 100e
  • i passaggi di convalida e MoneyWithdrewEvent di 100e vengono emessi
  • nel frattempo, la persona A emette un altro WithdrawCommand of 100e
  • il primo MoneyWithdrewEvent non è stato aggregato e pertanto i passaggi di convalida, perché il controllo di convalida rispetto all'aggregato (che non è ancora stato aggiornato)
  • MoneyWithdrewEvent di 100e viene emesso un'altra volta

== > Siamo in uno stato incoerente di un saldo a -100e e il log contiene 2 MoneyWithdrewEvent

Come ho capito ci sono diverse strategie per far fronte a questo problema:

  • a) inserisce l'ID della versione aggregata insieme all'evento nell'archivio degli eventi, quindi se c'è una versione non corrispondente alla modifica, non accade nulla
  • b) utilizza alcune strategie di blocco, il che implica che il livello di verifica deve in qualche modo crearne uno

Domande relative alle strategie:

  • a) In questo caso, il registro eventi non è più la fonte della verità, come affrontarlo? Inoltre, siamo tornati al client OK mentre era totalmente sbagliato consentire il ritiro, è meglio in questo caso utilizzare i blocchi?
  • b) Serrature == deadlock, hai qualche informazione sulle migliori pratiche?

Nel complesso, la mia comprensione è corretta su come gestire la concorrenza?

Nota: capisco che la stessa persona che ritira due volte il denaro in una finestra così breve è impossibile, ma ho preso un semplice esempio, per non perdermi nei dettagli

    
posta Louis F. 24.05.2017 - 20:57
fonte

2 risposte

14

I sketched my rough understanding on how an ES / CQRS app should look like contextualized to a simplified banking use case (withdrawing money).

Questo è l'esempio perfetto di un'applicazione basata su eventi. Iniziamo.

Ogni volta un comando viene elaborato o riprovato (capirai, sii paziente) i seguenti passaggi sono eseguiti:

  1. il comando raggiunge un gestore di comandi, ovvero un servizio in Application layer .
  2. il gestore comandi identifica Aggregate e lo carica dal repository (in questo caso il caricamento viene eseguito da new -ing un'istanza Aggregate , recuperando tutti gli eventi precedentemente emessi da questo aggregato e riapplicando loro alla stessa Aggregazione, la versione aggregata viene archiviata per un uso successivo, dopo l'applicazione degli eventi l'aggregato si trova nel suo stato finale, ovvero il saldo del conto corrente viene calcolato come un numero)
  3. il gestore comandi chiama il metodo appropriato su Aggregate , come Account::withdrawMoney(100) e raccoglie gli eventi generati, cioè MoneyWithdrewEvent(AccountId, 100) ; se non ci sono abbastanza soldi nel conto (saldo < 100) allora viene sollevata un'eccezione e tutto viene interrotto; in caso contrario, viene eseguito il passaggio successivo.
  4. il gestore comandi tenta di mantenere il Aggregate nel repository (in questo caso il repository è Event Store ); lo fa accodando i nuovi eventi a Event stream se e solo se il version di Aggregate è ancora quello in cui è stato caricato Aggregate . Se la versione non è la stessa, , quindi il comando viene ritentato, vai al passaggio 1 . Se version è uguale, gli eventi vengono aggiunti a Event stream e al client viene fornito lo stato Success .

Questo controllo di versione è chiamato locking ottimistico ed è un meccanismo di blocco generale. Un altro meccanismo è blocco pessimistico quando altri scritti sono bloccati (come in non iniziato) fino a quando non viene completato quello corrente.

Il termine Event stream è un'astrazione attorno a tutti gli eventi emessi dallo stesso Aggregato.

Devi capire che Event store è solo un altro tipo di persistenza in cui sono memorizzate tutte le modifiche a un aggregato, non solo lo stato finale.

a) In this case, the event log is not the source of truth anymore, how to deal with it ? Also, we returned to the client OK whereas it was totally wrong to allow the withdrawal, is it better in this case to use locks ?

L'archivio eventi è sempre la fonte della verità.

b) Locks == deadlocks, do you have any insights about the best practices ?

Usando il blocco ottimistico non hai blocchi, basta comandare di riprovare.

Anyways, Locks! = deadlock

    
risposta data 25.05.2017 - 08:44
fonte
1

I sketched my rough understanding on how an ES / CQRS app should look like contextualized to a simplified banking use case (withdrawing money).

Chiudi. Il problema è che la logica per aggiornare il tuo "aggregato" è in un posto strano.

L'implementazione più usuale è che il modello di dati che il tuo gestore comandi conserva in memoria e il flusso di eventi nell'archivio eventi sia mantenuto sincronizzato.

Un semplice esempio da descrivere è il caso in cui il gestore comandi effettua scritture sincrone nell'archivio eventi e aggiorna la sua copia locale del modello se la connessione all'archivio eventi indica che la scrittura è riuscita.

Se il gestore comandi deve risincronizzare con l'archivio eventi (perché il suo modello interno non corrisponde a quello dell'archivio), lo fa caricando la cronologia dall'archivio e ricostruendo il proprio stato interno.

In altre parole, le frecce 2 e 3 (se presenti) sarebbero normalmente collegate all'archivio degli eventi, non a un archivio aggregato.

put the aggregate version id along with the event in the event store so if there is a version mismatch upon modification, nothing happens

Le varianti di questo sono il solito caso - piuttosto che aggiungendo allo stream nel flusso di eventi, tipicamente PUT in una posizione specifica nello stream; se tale operazione non è compatibile con lo stato dell'archivio, la scrittura non riesce e il servizio può scegliere la modalità di errore appropriata (non riescono a client, riprovare, unire ....). L'utilizzo di scritture idempotenti risolve un numero di problemi nella messaggistica distribuita, ma ovviamente richiede un negozio che supporti una scrittura idempotente.

    
risposta data 24.05.2017 - 21:50
fonte

Leggi altre domande sui tag