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