Come evitare di implementare due volte le proiezioni di Event Sourced?

6

Attualmente sto insegnando a me stesso il sourcing di eventi e ho ottenuto il concetto abbastanza per iniziare a sviluppare un'app fittizia in C # ed EventStore. La mia app è un sistema di conto bancario facile da capire.

Se modelliamo un conto bancario come una serie di eventi di prelievo e deposito, possiamo facilmente calcolare il saldo corrente dell'account sommando tutti i depositi e sottraendo tutti i prelievi, e EventStore ci dà un modo per implementarlo nei dati memorizzare utilizzando proiezioni .

Tuttavia, se abbiamo una regola aziendale che impone che gli account non possano mai essere sovrasfruttati (ovvero rifiutare le richieste di prelievo da un account se il saldo attuale non è sufficiente a coprire i fondi richiesti), allora anche l'entità BankAccount deve conoscere il suo attuale equilibrio.

Ancora una volta, questo è facile da calcolare eseguendo la serie di eventi di conti bancari, ma ho un profondo disagio con il fatto che ho dovuto implementare la mia proiezione di "equilibrio" due volte: una volta nell'archivio dati e una volta in il dominio.

Questo è previsto da Event Sourcing e non dovrei preoccuparmene? Oppure sto seguendo il percorso sbagliato, e in effetti il saldo non dovrebbe essere calcolato nel codice di dominio e dovrebbe invece essere recuperato dall'archivio dati ogni volta che è necessario? Ciò significa che ogni volta che un'entità viene modificata, sarà necessario effettuare un round trip nell'archivio dati.

Ho visto nei discorsi di Greg Young che dovrebbe essere una scelta naturale per DDD quindi non dovrebbe esserci un'incompatibilità lì.

Se ho frainteso qualsiasi parte di questa configurazione, qualsiasi aiuto sarebbe molto apprezzato.

    
posta Richiban 16.02.2018 - 17:58
fonte

3 risposte

5

Il registro degli eventi è la tua unica fonte di verità. Non ci si può fidare dei dati nelle proiezioni, perché alla fine è coerente e non deve essere aggiornato. Come hai detto, puoi effettivamente ottenere l'ultimo stato della tua entità di dominio leggendo tutti gli eventi relativi a quella specifica entità e convertendoli nella tua rappresentazione finale.

Quindi, se un account non può mai essere sovrascritto, è necessario conoscere il suo saldo, è necessario calcolarlo nell'oggetto dominio dal flusso di eventi. Questo non è solo raccomandato ma necessario.

Naturalmente, se le tue proiezioni sono tanto semplici quanto l'ultima (eventualmente coerente) rappresentazione di stato delle tue entità, potrebbe sembrare che tu stia duplicando la logica. Tuttavia, lo stato più recente di un'entità nella maggior parte dei casi non è la sola rappresentazione dell'entità, cioè come vorresti vederli i dati.

Per diversi scopi è possibile (e con grandi sistemi sono suscettibili di) avere proiezioni diverse. Alcuni possono essere entità, ma altre proiezioni possono essere intere aggregazioni. Con queste aggregazioni potresti quindi utilizzare un evento MoneyWasDeposited da tutte le entità BankAccount per aggiornare un flusso di cassa mensile per vedere quanti soldi stanno effettivamente scorrendo nel tuo sistema.

La forza delle proiezioni arriva nel momento in cui scopri che hai bisogno di diverse rappresentazioni di - internamente - gli stessi dati. Grazie alla pre-creazione della proiezione, l'aumento della complessità della vista non rallenta i tempi di caricamento per gli utenti finali.

Per dare seguito alla risposta di VoiceOfUnreason, mentre ad es. condividere la logica per il calcolo dell'equilibrio ha senso in termini di principio di ESSICCAZIONE (in modo che non ti ripeta), quando in realtà praticando ES, ho imparato che per lo più non lo fa.

Un evento potrebbe essere simile a questo:

class MoneyWasDeposited: IEvent
{
    public BankAccountId ToBankAccountId;
    public Money Amount;
}

Un aggregato generato da un evento di solito elaborava questo evento utilizzando un meccanismo simile a questo:

class BankAccount
{
    private BankAccountId id;
    private Money currentBalance;

    public void ReplayFromEvents(List<IEvent> events)
    {
        for (var event in Events)
        {
            ApplyEvent(event);
        }
    }

    private void ApplyEvent(IEvent event)
    {
        if (event is MoneyWasDeposited)
        {
            currentBalance = currentBalance.Add(event.Amount);
        }
    }
}

mentre è probabile che la proiezione sia simile a questa:

class BankAccountProjection
{
    private MySqlConnection database;

    public void HandleMoneyWasDeposited(MoneyWasDeposited event)
    {
        var statement = database.Prepare("
            UPDATE
                bank_accounts
            SET balance = balance + :depositedAmount
            WHERE id = :id
        ");

        statement.BindValue("depositedAmount", event.Amount.Value);
        statement.BindValue("id", event.ToBankAccountId.ToString());

        statement.ExecuteNoResult();
    }
}

Quindi, mentre il tuo aggregato calcola il valore in codice dall'evento, la proiezione di solito applica la modifica direttamente al tuo negozio di read-model e lascia che il motore di archiviazione si occupi della modifica dei dati.

Come puoi vedere, non c'è davvero alcun modo per ridurre la duplicazione con questo, perché l'aggregazione e la proiezione utilizzano meccanismi molto diversi per ottenere il risultato desiderato.

Ora, non sto dicendo che non è possibile estrarre l'intera proiezione dall'archivio del modello di lettura, aggiornare il modello di lettura in memoria e scaricarlo per l'aggiornamento, ma di solito non è il modo in cui è fatto, come aggiunge un inutile SELECT e ottiene comunque lo stesso risultato.

Potresti chiedere: la logica di calcolo del saldo non è in un singolo posto. Quindi, cosa succede se qualcuno, per errore, rende la logica della proiezione errata?

Considerare lo scenario in cui qualcuno ha scritto un segno meno all'aggiornamento del saldo delle proiezioni quando il denaro è stato depositato. Ciò significherebbe che ogni volta che hai depositato denaro sul tuo account, all'improvviso ne avresti di meno.

Dovresti quindi eseguire le seguenti operazioni:

  • cambia il segno meno in un segno più nel metodo di proiezione,
  • sposta immediatamente la modifica sulla produzione, in modo che tutte le modifiche da ora in poi siano corrette,
  • crea un nuovo indice di proiezione nel tuo negozio di modelli di lettura, ad es. bank_accounts_fixed ,
  • esegui un'altra istanza di gestore di proiezione conto bancario dall'inizio del tuo sistema e inserisci invece i dati in bank_accounts_fixed ,
  • dopo che la proiezione locale è terminata, scambia la configurazione in produzione per utilizzare i dati di bank_accounts_fixed al posto di bank_accounts_fixed ,
  • elimina l'indice bank_accounts originale.

E tutti i tuoi utenti vedranno improvvisamente saldi corretti. Puoi farlo perché sai che i dati nel tuo archivio eventi sono garantiti per essere corretti (dovrebbe essere).

    
risposta data 16.02.2018 - 18:22
fonte
0

Again, this is easy to calculate by running over the series of bank account events but I have a deep unease with the fact that I have had to implement my projection of "balance" twice: once in the data store and once in the domain.

In genere, si acquisisce l'idea che deposits e withdrawals si sommano a balance in un'unica posizione e invochino quel codice da altre parti del dominio come inutili.

Se stai cercando di utilizzare un framework di proiezione, condividere una singola implementazione può essere scomodo.

Depositi, prelievi e saldo fanno parte della descrizione dei dati nel tuo dominio; non dovrebbero essere un grande segreto noto solo a una parte del dominio o di un altro.

Cosa fare quando arriva un input di prelievo che inserisce Balance in uno stato negativo è incapsulato nella logica del dominio; potrebbe consentire al saldo di andare in negativo, rifiutare l'input, memorizzare l'input in qualche altro luogo in attesa di chiarimenti, o accettare l'input e anche contrassegnare l'account come in violazione della politica di scoperto (banche come per riscuotere le tasse, ricorda).

Nota a margine: devi essere molto cauto nel modellare qualcosa come bancario per pensare se puoi porre il veto su un particolare messaggio. Un bancomat potrebbe rifiutarsi di effettuare un pagamento quando pensa che farlo violerebbe le regole aziendali, ma se un bancomat ha effettuato un pagamento che si desidera non fosse, probabilmente non si limiterà a buttare via i dati.

I am not asking about the particulars of command failure but instead where the balance calculation should be implemented: domain, data store, or both?

È lo stato. Potrebbe essere implicito (hai calcolato il saldo mentre applichi gli eventi che tracciano ciascun deposito e prelievo), o esplicito (includi una rappresentazione del saldo "corrente" negli eventi di deposito / prelievo e applicalo).

Il codice di dominio dovrà essere in grado di accedervi, se il valore corrente verrà utilizzato nella logica di gestione di un comando. I tuoi modelli letti ne avranno bisogno se i dati sono visibili ai consumatori a valle (comprese le visualizzazioni di rendering per operatori umani).

Quindi sì, potresti finire per duplicare il calcolo, ad esempio se il tuo modello di dominio è implementato in una lingua, ma le tue proiezioni sono implementate in una lingua diversa.

Nel caso più generale, l'aggregato può spostarsi tra diversi stati (emettere diversi eventi) durante l'elaborazione di un singolo comando. Quando ciò accade, il modello di solito deve essere in grado di calcolare gli stati intermedi senza l'aiuto dell'archivio dati. Quindi la logica richiesta per calcolare i nuovi stati sarà invocata sia prima che gli eventi vengano scritti nel negozio, sia dopo.

    
risposta data 16.02.2018 - 21:11
fonte
0

Non conosco specificamente EventStore, ma ti dà proiezioni come pieghe, il che è bello.

Tuttavia, il calcolo del saldo è ancora logicamente di proprietà della logica del core business / dominio, quindi assicurati che il modello di dominio arrivi a specificare il proiezione .

Il tuo modello di dominio può anche offrire un'operazione di saldo dell'account, che a sua volta utilizza la proiezione.

(Se si desidera utilizzare ES in modo più uniforme con CQRS, è possibile disporre di un modello di scrittura di dominio normalizzato completamente aggiornato che mantenga il saldo corrente degli account, che l'archivio dati possa essere gettato via e ricostruito da ES a in qualsiasi momento e l'implementazione del saldo dell'account può utilizzare il contenuto memorizzato nella cache da questo modello di scrittura.)

    
risposta data 17.02.2018 - 02:22
fonte

Leggi altre domande sui tag