DDD: gli oggetti che rappresentano "radici aggregate" devono essere univoci (tipo singleton)

5

Sto sviluppando la mia prima applicazione utilizzando DDD e event sourcing.

Da quanto ho capito, l'aggregazione incapsula funzionalità di dominio specifiche con uno o più invarianti associati. Gli invarianti sono tradotti in vincoli di coerenza: in qualsiasi momento lo stato dell'aggregato deve essere coerente (gli invarianti sono conservati).

Fin qui tutto bene.

La maggior parte delle esercitazioni che ho visto suggeriscono che le radici aggregate dovrebbero essere ottenute in questo modo:

UserAggregate user = mUsersRepository.getUser(userId);

A questo punto mi sento confuso.

Supponiamo che ogni chiamata a UsersRepository restituisca una nuova istanza di UserAggregate ("approccio 1"). Questo può essere utile perché elimina la necessità di garantire la sicurezza del thread (nel codice). Tuttavia, se più oggetti UserAggregate verranno utilizzati contemporaneamente, questi oggetti potrebbero non essere sincronizzati tra loro (pur rimanendo coerenti singolarmente). Ho la sensazione che avere diverse istanze "non sincronizzate" dello stesso aggregato possa portare a tutti i tipi di bug sgradevoli, ma non posso davvero esserne certo.

Ora supponiamo che ogni chiamata a UsersRepository restituisca lo stesso oggetto UserAggregate ("approccio 2"). Quindi l'istanziazione e la memorizzazione nella cache di UserAggregate all'interno di UserRepository devono essere rese thread-safe. Inoltre, la memorizzazione nella cache deve basarsi su riferimenti deboli; in caso contrario, l'intero database degli utenti verrà tenuto in memoria. La mia preoccupazione principale, tuttavia, è che in questo scenario UserAggregate stesso debba diventare thread-safe. Sembra che sarà un importante PITA per garantire la sicurezza del thread di tutte le radici aggregate. Può anche diventare un problema di prestazioni. Non menzionare il rischio associato a bug multi-threading ...

Se fosse tutto, probabilmente andrei con "approccio 2" perché, almeno, so come renderlo sicuro. Tuttavia, esiste questa nozione di blocco ottimistico basato sulla versione aggregata che (a mio modo di vedere) si applica a "approccio 1".

Se ho capito bene, a ogni aggregato viene associata una versione. Ad ogni aggiornamento dello stato di aggregazione viene verificata la versione e, se la versione in DB non è uguale alla versione dell'aggregazione memorizzata nella cache, l'aggiornamento non riesce. Quindi lo stato aggregato può essere sincronizzato dal database e l'aggiornamento riprovato. Questo sembra indirizzare le mie paure legate all'incongruenza tra le diverse istanze dell'aggregato. Tuttavia, sembra molto lavoro - ogni metodo che aggiorna l'aggregato può fallire, quindi il codice che lo utilizza deve essere in grado di riprovare l'operazione.

Le mie domande sono:

  1. La mia comprensione (riassunta sopra) è corretta?
  2. Ci sono altri problemi associati all'approccio 1/2 che non vedo?
  3. Ci sono altri approcci che non vedo?
  4. Esiste un approccio standard utilizzato dalla comunità DDD?
posta Vasiliy 04.06.2017 - 16:49
fonte

1 risposta

4
UserAggregate user = mUsersRepository.getUser(userId);

At this point I become confused.

Sì, è un po 'un groviglio.

However, if multiple UserAggregate objects will be used simultaneously, these objects might get out of sync with each other (while staying consistent individually). I get a feeling that having several "non-synchronized" instances of the same aggregate can lead to all kinds of nasty bugs, but I can't really be sure about it.

Ci sono due preoccupazioni.

Il primo è che le operazioni che leggono lo stato dell'aggregato potrebbero leggere una vecchia copia. Per la maggior parte, la letteratura riconosce che ciò significa semplicemente che le letture sono "alla fine coerenti". È analogo a lavorare con le risorse mutabili sul Web - Alice POST è una modifica alla risorsa, ma Bob non vede quel cambiamento immediatamente perché ha una copia memorizzata nella cache.

Il schema CQRS arriva fino al punto di supportare completamente le query (letture) dal modello il ruolo delle tue radici aggregate è garantire che la coerenza venga mantenuta durante le scritture.

Il secondo è che se abbiamo più scrittori (due thread, possibilmente in processi diversi, possibilmente su host diversi) eseguendo una scrittura simultaneamente , possiamo finire con un cervello diviso; "l'aggregato in due stati diversi a seconda di dove si guarda, e potremmo perdere le modifiche quando uno viene salvato sopra l'altro.

Questo è il problema che è "reale" e deve essere affrontato.

Una risposta è limitarsi a un singolo scrittore. L' architettura LMAX è un esempio di questo: tutti i comandi sono linearizzati pubblicandoli in una coda e "lo" scrittore quindi consuma la coda e gestisce tutte le scritture. Quindi risolvi il problema dei conflitti non avendo alcuno - o più correttamente accetti un problema di elezione dei leader in cambio del problema dei conflitti.

Con più scrittori; la solita risposta è riconoscere che il database (che si tratti di un RDBMS o di un elemento No-SQL o di un file system) è il libro dei record - questo è quello che userete come "verità" se il potere si spegne . Pertanto, i tuoi scrittori stanno condividendo il database e devi implementare l'integrità di scrittura lì.

So solo due risposte: i tuoi scrittori corrono per afferrare un lucchetto, e il vincitore ottiene l'autorizzazione alla scrittura finché il blocco non viene rilasciato, oppure i tuoi scrittori corrono per confrontare e scambiare.

If I understand it correctly, each aggregate gets a version associated with it. On each update of aggregate's state the version is checked, and, if the version in DB is not the same as the version of the cached aggregate, the update fails.

Questo è "confronta e scambia", almeno logicamente. Potrebbe essere necessario giocare a giochi con numeri di versione, o posizioni di flusso, o simili, per ottenere effettivamente un'implementazione funzionante, ma logicamente sono tutte varianti di "cambiare lo stato corrente da quello che si trova lì a quello che si trova qui ". Conditional PUT, per così dire.

However, it feels like a lot of work - each method that updates the aggregate can fail, therefore the code that uses it must be capable of retrying the operation.

Ecco le "buone" notizie: in un sistema distribuito, dovevi preoccuparti di quel problema comunque . Il database potrebbe non essere disponibile, potrebbe essere sovraccarico, il filesystem potrebbe essere pieno, ecc. Funziona, ma non è extra lavoro.

Is there a standard approach that DDD community uses?

La maggior parte delle persone che stanno facendo il sourcing di eventi tende a utilizzare il confronto e lo scambio, specialmente quando si utilizzano archivi di eventi NoSql che supportano scritture idempotent.

Ma la mia sensazione è che ES sia ancora una minoranza relativa nello spazio; per me è meno chiaro quale approccio le persone che stanno salvando lo stato di aggregazione stanno usando.

    
risposta data 05.06.2017 - 06:11
fonte

Leggi altre domande sui tag