Il riferimento per ID tra Aggregati porta a un modello di dominio anemico?

5

Panoramica

Sto cercando di comprendere il modo migliore per collegare Aggregate Roots insieme senza trasferire gran parte della logica di business dalle Entità / AR a Servizi , mentre continui ad aderire al suggerimento di Vaughn Vernon per:

Prefer references to external Aggregates only by their globally unique identity, not by holding a direct object reference (or “pointer”) .....

Dettagli

Prendiamo ad esempio un semplice sistema di ricerca. Questo sistema di ricerca consente agli utenti di eseguire una ricerca indipendente per un cliente contro un elenco di persone sospette.

Permette anche di eseguire Ricerche Batch che è semplicemente una raccolta di Ricerche contro un elenco di Clienti

Ho modellato quanto sopra in questo modo.

Insostanzail

  • Cerca
  • Ricercabatch

sonoentrambiRootsaggregati.Unaricercapuòessereeseguitainmodoindipendentemapuòancheessereeseguitacomepartediunaricercabatch,nelqualcasolaricercabatchcreataconterràRicerche.

Lacodificaèabbastanzasemplice

class BatchSearch { constructor(customers) { this.searches = [] this.customers = customers } run() { for (customer of this.customers) { const search = new Search(customer) search.run() this.searches.push(search) } this.markAsCompleted() } ... }

Tuttavia, Vaughn Vernon afferma che mantenere un riferimento diretto tra le Radici complessive è un cattivo progetto.

Da, Disegno aggregato efficace Parte II: Aggregazione degli aggregati ":

Prefer references to external Aggregates only by their globally unique identity, not by holding a direct object reference (or “pointer”) ..... Use a Repository or Domain Service (7) to look up dependent objects ahead of invoking the Aggregate behavior

A quanto ho capito, sostiene le interazioni in movimento tra gli Aggregati nei Servizi in questo modo:

class BatchSearchService {
  constructor() {
  }

  createBatchSearch(customers) {
    let searches = []
    const batchSearch = new BatchSearch()

    for (customer of customers) {
      const search = new Search(customer)
      // We link this Search with this Batch Search by ID only, here
      search.setBatchSearchId(batchSearch.getId())
      search.run()
      searches.push(search)
    }

    batchSearch.markAsCompleted()

    batchSearchRepo.save(batchSearch)
    searchRepo.save(searches)
  }
}

Questa raccomandazione non porta inevitabilmente a un modello di dominio anemico ?

AFAIK OOP è fondamentalmente l'accoppiamento di dati e operazioni in una classe, ma da quello che ho capito in questo scenario le operazioni vengono spostate dalla classe di ricerca in batch al servizio di ricerca batch invece, lasciando la Classe di ricerca batch solo per contenere i dati

    
posta Nik Kyriakides 20.08.2017 - 21:58
fonte

3 risposte

8

La raccomandazione di Vaughn Vernon è una delle migliori regole tattiche da seguire. Se hai davvero bisogno di contenere un riferimento a un'altra radice aggregata, devi rivedere il limite del tuo aggregato come molto probabilmente sono sbagliati.

In questo caso il modello è anemico perché il business è anemico , non ha invarianti che è necessario proteggere, almeno da quello che hai presentato. In ogni caso, aggregates dovrebbe essere usato sul lato scrittura / comando della tua architettura; Lo sto specificando perché, in quasi tutti i domini, "search" è un'operazione di lettura / query, ma suppongo che il tuo dominio sia speciale in cui una "ricerca" implica una mutazione di stato.

    
risposta data 21.08.2017 - 06:20
fonte
1

Non penso che tu abbia in realtà due radici aggregate qui. Una radice aggregata è un'entità - che significa che ha "continuità e identità" a livello di dominio. Dalla tua descrizione, dai commenti e dagli esempi di codice, sembra che solo il tipo Search sia in realtà un'entità. BatchSearch sembra mancare di tale identità (almeno, nessun ID simile appare nella prima versione del codice), il che probabilmente lo rende un servizio (DDD), specialmente se non ha uno stato visibile esternamente. Potrebbe anche essere trattato come un oggetto valore, a seconda di come lo si utilizza. Ora, se si tratta di un servizio, si noti che questo non significa che si dovrebbe spostare la funzionalità in qualche metodo di utilità; i servizi possono essere implementati come oggetti (ed essere polimorfici, avere dipendenze, ecc.).

I punti di Vaughn Vernon riguardano il caricamento e la modifica dello stato degli aggregati. Un aggregato è un gruppo di entità correlate e oggetti valore che ha un'entità esternamente visibile (la radice aggregata - essenzialmente un Facciata ), che ha due scopi: (1) definisce le regole di attraversamento e nasconde la complessità e (2) fornisce alcune garanzie sulla consistenza dell'aggregato nel suo complesso quando subisce delle modifiche. Nell'articolo a cui ci si è collegati, il problema ruota attorno all'idea che due aggregati associati non debbano essere modificati in una singola operazione atomica, poiché questo ha implicazioni di progettazione e prestazioni (supponendo che la coerenza atomica sia l'obiettivo).

Quindi per evitare questa situazione, raccomanda di fare riferimento all'altra aggregazione (che, ancora una volta, è un'entità) dal suo ID di dominio. Ciò non impedisce completamente lo scenario problematico, ma lo rende più difficile da realizzare, consentendo comunque la navigazione verso l'altro aggregato. C'è anche l'ulteriore vantaggio di un ridotto ingombro della memoria, nel senso che i due aggregati vengono caricati indipendentemente.

Nella sezione Navigazione del modello parla di come è possibile utilizzare una forma di caricamento lento per supportare la navigazione tra le radici aggregate e procede a raccomandare "utilizzare un repository o un servizio di dominio per cercare oggetti dipendenti prima di invocare il comportamento aggregato ", anziché cercare le dipendenze tramite un repository dall'interno di un aggregato, che è in realtà solo un'applicazione del principio di inversione delle dipendenze (dove questo" repository o servizio di dominio "viene utilizzato per eseguire manualmente l'iniezione delle dipendenze).

Inoltre afferma che se questo approccio semplicemente non funziona, allora dovresti considerare di progettare il tuo sistema per un'eventuale coerenza (cioè, il sistema non dovrebbe presumere che un aggiornamento riuscito di un aggregato significhi che un anche il relativo aggregato è stato modificato di conseguenza, ed è in uno stato valido in quel momento), e quindi descrive un approccio basato sugli eventi per costruire un tale sistema.

Ora, la domanda è: qualcuna di queste cose si applica alla tua classe BatchSearch ? Ha un'identità non transitoria a livello di dominio? È meglio modellato come un servizio che costruisce e restituisce un gruppo di entità di dominio (% istanze diSearch)? Inoltre, lo stesso articolo sottolinea che ci sono casi in cui si può decidere di infrangere le regole, e elenca diversi motivi per farlo, che è anche qualcosa da considerare.

    
risposta data 26.08.2017 - 21:11
fonte
0

Un modo per unificare il modello sarebbe utilizzare Cerca come radice aggregata con il modello Invocazione come parte dell'aggregato.

Non è diverso da come hai già modellato, ma pensando a Ricerca e BatchSearch come due diversi modelli di aggregazione devono andare via.

Memorizza Cerca come risorsa con flag che rappresentano lo stato in batch o meno.

class Search {
    List<Invocation> invocationList;
    boolean: batched; 

    // you can check size of the list to determine whether it's batched or not - this simplifies the model further
}

class Invocation {
    List<SearchParameters> searchParameterList;

    ..
    //add more fields as required
    .. 
}

Progetta i tuoi casi d'uso e modellali intorno a trattare ciascuna chiamata come Cerca con un numero diverso di invocazioni (a seconda dei casi d'uso che potresti chiamare "Invocazione" in modo diverso).

Quindi i modelli resterebbero all'interno del limite della radice aggregata nel contesto specificato.

Spero che aiuti. Fammi sapere se devo tenere conto di eventuali problemi di progettazione mancanti.

    
risposta data 25.08.2017 - 19:43
fonte

Leggi altre domande sui tag