Eliminazione di sottoalberi in aggregati gerarchici

2

Sto lavorando su un'applicazione simile al file system in cui utilizzo CQRS / event-sourcing per gestire lo stato di cartelle e file (creazione, spostamento, ridenominazione, ecc.). Ogni nodo (cartella o file) è rappresentato da un singolo aggregato (in quanto unità autonoma con un proprio ID). Ora sto per implementare la cancellazione dei nodi, dove il sistema è in grado, ovviamente, di eliminare un'intera sottostruttura di nodi (= eliminare una cartella contenente altre cartelle e file).

Ora non so come gestirlo nello stile CQRS perché il comando, come ho appreso, dovrebbe funzionare su un singolo aggregato.

Un'idea che ho avuto è quella di creare una saga che sarebbe stata creata dall'evento FolderDeleteStarted e la responsabilità della saga sarebbe quella di creare comandi per ciascuno dei nodi figlio, con conseguente creazione a cascata di sagas per ogni cartella figlio. Saga si assicurerebbe di eliminare la cartella genitore una volta che tutti i comandi per le cartelle figlio sarebbero stati completati, quindi sarebbe stato bloccato. Saga sarebbe eseguito in thread separati, quindi il comando iniziale per l'eliminazione della cartella sarà ancora asincrono.

Tuttavia non mi piace l'idea di saghe a cascata.

Sono nuovo di CQRS e sto ancora imparando le basi. Mi piacerebbe sapere come risolvere una situazione simile.

    
posta redhead 28.09.2015 - 16:56
fonte

2 risposte

2

Ho provato saghe a cascata (in realtà i process manager in questo caso, ma manterrò il termine della saga qui sotto) con un certo successo. Ha funzionato in questo modo:

aggregate - DeleteNode 1  -> NodeDeleted 1
saga      - NodeDeleted 1 -> child nodes 2, 3
                             DeleteNode 2
                             DeleteNode 3
aggregate - DeleteNode 2  -> NodeDeleted 2
aggregate - DeleteNode 3  -> NodeDeleted 3
saga      - NodeDeleted 2 -> no child nodes
saga      - NodeDeleted 3 -> no child nodes

Problemi

  • Cicli (ad esempio se si seguono i collegamenti).
    • Assicurati che vengano rilevati ed esci in anticipo o si verificherà un ciclo infinito
    • Potrebbe essere necessario arricchire l'evento per includere precedenti percorsi di eliminazione per il rilevamento dei cicli nella saga
  • Multi-genitore (tramite link di nuovo).
    • Preparati a gestirlo nel complesso
      • Fallito 2 ° tentativo di cancellazione b / c è già cancellato
      • La tua strategia di concorrenza entra in gioco qui

Nel mio caso, ho avuto entrambi questi problemi inizialmente. Abbiamo corretto i dati per non avere cicli e aggiunto una convalida per prevenirli in futuro. Tuttavia, il multi-genitore di un nodo figlio era una funzionalità richiesta. Ha causato comandi extra inutili contro gli stessi sottonodi. C'era ancora un caso limite che non avevamo rintracciato dove la cascata si sarebbe fermata presto. È successo meno di 10 volte l'anno e abbiamo semplicemente eseguito un comando di ricalcolo sull'elemento per risolverlo.

Un modo migliore

Quanto sopra è complicato e presenta casi limite che potrebbero non essere coperti.

Invece di usare una saga a cascata, un'altra opzione è quella di far sì che il gestore di comandi per questo caso d'uso carichi tutti i nodi discendenti del nodo in fase di cancellazione e ne registrino gli ID (in realtà non chiamano l'eliminazione sui nodi figli qui come richiede una transazione che si estende su aggregati). Quindi, come parte del tuo use case delete sul tuo nodo aggregato, passa la matrice di tutti gli id dei nodi discendenti. Il tuo aggregato probabilmente conosce i suoi ID figli immediati, ma non tutti i discendenti.

Quindi la tua saga avrebbe informazioni sufficienti per inviare comandi per eliminare tutti i discendenti contemporaneamente. Sarebbe un comando di cancellazione diverso in modo da non essere ricorsivo, perché hai già fatto il lavoro ricorsivo in anticipo quando l'utente ha richiesto l'eliminazione.

aggregate DeleteNode 1 [2,3] -> NodeDeleted 1 [2,3]
saga      NodeDeleted 1 [2,3] -> DeleteNodeInternal 3
                                 DeleteNodeInternal 2
aggregate DeleteNodeInternal 3 -> NodeDeletedInternal 3
          DeleteNodeInternal 2 -> NodeDeletedInternal 2

- Aggiornamento -

Questo metodo sopra non copre ancora il caso in cui i nodi vengono aggiunti ai discendenti del genitore eliminato mentre la saga sta ancora inviando i comandi di cancellazione. Per coprire ciò, è possibile mantenere una struttura ad albero dei nodi eliminati nell'evento di eliminazione iniziale. Quindi, quando la saga tenta di eliminare un nodo, invia con sé i figli attesi di quel nodo. Quando i bambini attesi e i bambini effettivi non corrispondono, quindi emettere un altro evento per la saga per risolvere quella situazione.

aggregate DeleteNode 1 [2,3] -> NodeDeleted 1 [2,3]
saga      NodeDeleted 1 [2,3] -> DeleteNodeInternal 3
                                 DeleteNodeInternal 2
aggregate DeleteNodeInternal 3 [] -> NodeDeletedInternal 3
                                     ChildEscapedDeletion 4
          DeleteNodeInternal 2 [] -> NodeDeletedInternal 2
saga      ChildEscapedDeletion 4 -> DeleteNode 4
    
risposta data 28.09.2015 - 18:15
fonte
1

Due approcci:

  • Il modo semplice: il gestore comandi elimina i figli prima del genitore. Quindi genitore. Quindi conferma l'eliminazione.
  • Bassa (e fissa) latenza per le eliminazioni: usa un diario. Il gestore comandi aggiunge voci nel diario (uno per ogni figlio) ed elimina padre. Quindi, un'attività asincrona, per ogni voce nel journal:
    1. Ottieni la voce (un genitore)
    2. Aggiungi voci nel diario (una per ogni bambino)
    3. Elimina la voce
risposta data 24.11.2015 - 10:35
fonte

Leggi altre domande sui tag