DDD: una radice aggregata è responsabile dell'eliminazione delle sue entità figlio dal loro repository?

1

Sto sviluppando un progetto software di grandi dimensioni che utilizza DDD (Domain-Driven Design). Ho un'entità che funge da radice aggregata, chiamiamola Root . Questa entità fa riferimento a una raccolta di entità figlio del tipo Child .

Poiché l'utilizzo di questa entità Child è un dettaglio di implementazione che non interessa ai client della mia entità Root , questi figli sono gestiti esclusivamente dall'entità Root , ovvero dalla creazione di nuovi figli e dalla rimozione dei bambini esistenti si svolge tutto secondo metodi dell'entità Root . Pseudocode potrebbe apparire come segue

class Root 
{
    private Collection<Child> children;

    public void addChild(ChildParameters params) 
    {
        this.children.append(new Child(params));
    }

    public void removeChildByCriteria(Criteria removalCriteria)
    {
       Child foundChild = this.children.findByCriteria(removalCriteria);
       if (foundChild) {
           this.children.remove(foundChild);
       }
    }
}

Penso che questo sia un approccio molto pulito se si guarda solo al modello dell'oggetto. Tuttavia, questo deve essere persistito da qualche parte, quindi un Repository si occupa di memorizzare un'entità Root e i suoi figli in un database.

Ora sto lottando per trovare il posto giusto in cui la logica rimuove un bambino persistente dal repository (e quindi dal database). Va bene lasciare che l'implementazione del repository trovi bambini orfani e rimuoverli automaticamente? O è la cancellazione della logica di dominio dei bambini orfani che dovrebbe essere esplicitamente codificata all'interno dell'entità root passando un repository al metodo removeChildByCriteria come

// ...
public void removeChildByCriteria(Criteria removalCriteria, ChildRepositoryInterface childRepository) 
{
    Child foundChild = this.children.findByCriteria(removalCriteria);
    if (foundChild) {
        this.children.remove(foundChild);
        childRepository.remove(foundChild);
    }
}
// ...

O è sbagliato posizionare la logica per gestire la gerarchia all'interno dell'entità root e invece un servizio di dominio o eventi di dominio e secondo gli ascoltatori dovrebbero essere usati?

    
posta Subsurf 30.04.2018 - 13:35
fonte

3 risposte

3

Dovresti esaminare gli argomenti di Udi Dahan in Do not Delete - Just Do not .

Naturalmente, da allora GDPR è diventato una cosa. Quindi potresti voler includere Forget Me come un concetto di dominio.

Is it ok to let the implementation of the repository find orphaned children and remove them automatically?

Mi sembra perfettamente accettabile.

Evans, nel libro blu, scrive

A delete operation must remove everything within the AGGREGATE boundary at once.

Sono un po 'deluso dalla mancanza di attenzione che paga all'argomento nel libro.

Evans descrive REPOSITORY come un'astrazione di una collezione in memoria. Fondamentalmente, un'operazione di cancellazione interrompe il collegamento tra la chiave (identificatore) e la radice aggregata, che consente alla radice aggregata di essere eventualmente garbage collection in un modo agnostico del dominio.

Se il tuo backing store era in realtà un RDBMS, piuttosto che una raccolta in memoria, allora un'eliminazione a cascata è un'approssimazione abbastanza ragionevole. Ciò potrebbe essere implementato nell'archivio dati stesso o scritto nella logica repository / ORM.

Ma se il tuo protocollo di cancellazione richiede azioni specifiche del dominio - l'invio di messaggi ad altri sistemi, e così via, probabilmente dovresti avere un metodo all'interno dell'aggregato che esprime quella logica. Trasformare una rappresentazione di un aggregato attivo in una rappresentazione di un aggregato di fine vita è una responsabilità del modello di dominio.

"Elimina" potrebbe ragionevolmente indicare una o entrambe le azioni, ma le responsabilità per esse sono chiaramente separate.

Modifica

Mi mancava questo al primo passaggio

its child entities from their repository?

Le entità figlio normalmente non hanno un proprio repository. Una volta stabilito che un'entità appartiene a un aggregato, è subordinata alla radice aggregata. Normalmente organizzerai che tutti delle entità nell'aggregato siano caricati dal repository usato per caricare la root.

    
risposta data 30.04.2018 - 14:25
fonte
0

Il tuo oggetto Root deve mantenere la sua coerenza. Ad esempio, rimuovi i bambini dalla raccolta, se necessario.

Il repository è responsabile della persistenza degli oggetti root. Quindi quando aggiungi un Root al repository e lo estrai di nuovo, i figli nella root che hai estratto dovrebbero essere gli stessi dei bambini nella root che hai inserito!

Ora, sta a te scegliere Quando persisti Roots. Un approccio è quello di iniettare il repository e aggiornare la root in esso dopo ogni modifica. Ma a mio avviso questo è un po 'problematico.

È meglio continuare a perseguire la radice al di fuori della propria logica, nell'app o nel servizio che lo sta utilizzando. Questo ti dà molto più controllo su come stai usando l'oggetto. vale a dire

btn_loadDoc()
{
    this.doc = repo.getDoc(id)
}

btn_addParagraph()
{
    this.doc.paragraphs.add(p);
}

btn_abandonChanges()
{
    this.doc = repo.getDoc(id)
}

btn_save()
{
    repo.save(this.doc)
}

qui hai la possibilità di non salvare le tue modifiche, dove come se avessi iniettato il repository e chiamato su paragraphs.add() dovresti annullare le modifiche sul db

    
risposta data 30.04.2018 - 13:50
fonte
0

Is it ok to let the implementation of the repository find orphaned children and remove them automatically?

Sì, è un modo comune per farlo. In pratica, spesso si riduce a lasciare che un ORM o eliminare i vincoli DB a cascata facciano la loro magia.

Dal punto di vista del consumatore, dopo aver chiamato Repository.Delete(entity) non devi più avere un riferimento all'entità. Il consumatore non è interessato a sapere come vengono eliminate le entità figlio.

    
risposta data 30.04.2018 - 15:00
fonte

Leggi altre domande sui tag