2009-03-16 7 views
7

Mi sembra di essere diventato un po 'confuso di tutto questo business DDD \ LinqToSql. Sto costruendo un sistema usando POCOS e linq a sql e ho repository per le radici aggregate. Quindi, ad esempio se si hanno le classi Order-> OrderLine, si dispone di un repository per Order ma non OrderLine in quanto Order è la radice dell'aggregato. Il repository ha il metodo delete per cancellare l'Ordine, ma come si eliminano i OrderLines? Avresti pensato di avere un metodo su Ordine chiamato RemoveOrderLine che ha rimosso la linea dalla collezione OrderLines, ma ha anche bisogno di cancellare il OrderLine dalla tabella l2s sottostante. Dato che non esiste un repository per OrderLine, come si dovrebbe fare?Domain Driven Design (da Linq a SQL) - Come si eliminano le parti di un aggregato?

Forse dispongono di repostories pubblici specializzati per l'interrogazione delle root e dei repository generici interni che gli oggetti di dominio effettivamente utilizzano per eliminare elementi all'interno degli aggregati?

public class OrderRepository : Repository<Order> { 
    public Order GetOrderByWhatever(); 
} 

public class Order { 
    public List<OrderLines> Lines {get; set;} //Will return a readonly list 
    public RemoveLine(OrderLine line) { 
     Lines.Remove(line); 
     //************* NOW WHAT? *************// 
     //(new Repository<OrderLine>(uow)).Delete(line) Perhaps?? 
     // But now we have to pass in the UOW and object is not persistent ignorant. AAGH! 
    } 
} 

Mi piacerebbe sapere quello che gli altri hanno fatto come io posso essere il solo alle prese con questo .... spero .... Grazie

risposta

2

Si chiama il RemoveOrderLine sull'Ordine che chiama la logica correlata. Questo non include le modifiche alla versione persistente.

In seguito si chiama un metodo di salvataggio/aggiornamento sul repository, che riceve l'ordine modificato. La sfida specifica diventa nel sapere che cosa è cambiato nell'oggetto dominio, che ci sono diverse opzioni (sono sicuro che ci sono più di quelli che ho le liste):

  • hanno per oggetto dominio tenere traccia delle modifiche, che includerebbe tenere traccia del fatto che x deve essere cancellato dalle righe dell'ordine. Anche qualcosa di simile al tracciamento dell'entità potrebbe essere scomposto.
  • Carica la versione persistente. Avere il codice nel repository che riconosce le differenze tra la versione persistente e la versione in memoria ed eseguire le modifiche.
  • Carica la versione persistente. Avere il codice nell'aggregato di root, che ti dà le differenze dato un aggregato radice originale.
+0

Presumo che questa spiegazione indichi che non stai utilizzando un ORM direttamente sulle entità del tuo dominio. Speravo di essere in grado di evitare il lavoro extra che descrivi sopra, avendo linq per SQL persistere automaticamente le modifiche apportate all'oggetto dominio ... –

+0

y, questo è il vero problema. Ho fatto quanto sopra. Vedo sempre commenti su Nhibernate che supporta scenari più avanzati, ma non ho visto come funziona con questo tipo di scenario (che non è solo l'uso di POCO) – eglasius

+0

Grazie; anche se non terribilmente ideale (colpa di LTS, non la tua risposta) questo sembra confermare le mie stesse intuizioni su come avrei bisogno di fare questo ... – Funka

1

In primo luogo, è necessario esporre le interfacce per ottenere riferimenti alla radice di aggregazione (ad esempio Order()). Usa il pattern Factory per rinnovare una nuova istanza della radice di aggregazione (ad esempio Order()).

Con ciò detto, i metodi sul tuo Radice di aggregazione compongono l'accesso ai relativi oggetti correlati, non a se stesso. Inoltre, non esporre mai tipi complessi come pubblici sulle radici aggregate (cioè la raccolta IList di Lines() che hai specificato nell'esempio). Ciò viola la legge del decremento (sp ck), che dice che non è possibile "Dot Walk" per i metodi, come Order.Lines.Add().

E inoltre, si viola la regola che consente al client di accedere a un riferimento a un oggetto interno su una radice di aggregazione. Le radici aggregate possono restituire un riferimento di un oggetto interno. Finché, il client esterno non può tenere un riferimento a quell'oggetto. Ad esempio, la tua "OrderLine" si passa a RemoveLine(). Non è possibile consentire al client esterno di controllare lo stato interno del modello (ad esempio Order() e il suo OrderLines()). Pertanto, dovresti aspettarti che OrderLine sia una nuova istanza su cui agire di conseguenza.

public interface IOrderRepository 
{ 
    Order GetOrderByWhatever(); 
} 

internal interface IOrderLineRepository 
{ 
    OrderLines GetOrderLines(); 
    void RemoveOrderLine(OrderLine line); 
} 

public class Order 
{ 
    private IOrderRepository orderRepository; 
    private IOrderLineRepository orderLineRepository; 
    internal Order() 
    { 
    // constructors should be not be exposed in your model. 
    // Use the Factory method to construct your complex Aggregate 
    // Roots. And/or use a container factory, like Castle Windsor 
    orderRepository = 
      ComponentFactory.GetInstanceOf<IOrderRepository>(); 
    orderLineRepository = 
      ComponentFactory.GetInstanceOf<IOrderLineRepository>(); 
    } 
    // you are allowed to expose this Lines property within your domain. 
    internal IList<OrderLines> Lines { get; set; } 
    public RemoveOrderLine(OrderLine line) 
    { 
    if (this.Lines.Exists(line)) 
    { 
     orderLineRepository.RemoveOrderLine(line); 
    } 
    } 
} 

non dimenticate la vostra fabbrica per la creazione di nuove istanze dell'Ordine():

public class OrderFactory 
{ 
    public Order CreateComponent(Type type) 
    { 
    // Create your new Order.Lines() here, if need be. 
    // Then, create an instance of your Order() type. 
    } 
} 

tuo client esterno non ha il diritto di accedere direttamente al IOrderLinesRepository, attraverso l'interfaccia per ottenere un riferimento di un oggetto valore all'interno della radice di aggregazione. Ma cerco di bloccarlo forzando i miei riferimenti a tutti i metodi della Radice Aggregata. Pertanto, è possibile contrassegnare l'IOrderLineRepository come interno in modo che non sia esposto.

In realtà raggruppo tutte le mie creazioni di radice aggregata in più fabbriche. Non mi piaceva l'approccio di "Alcune radici aggregate avranno fabbriche per tipi complessi, altre no". Molto più facile avere la stessa logica seguita in tutta la modellazione del dominio. "Oh, così Sales() è una radice aggregata come Order(). Ci deve essere una fabbrica anche per questo."

Un'ultima nota è che se si dispone di una combinazione, ad esempio SalesOrder(), che utilizza due modelli di Sales() e Order(), si utilizzerà un servizio per creare e agire su quell'istanza di SalesOrder() poiché né le diramazioni aggregate Sales() o Order(), né i relativi repository o factory, hanno il controllo sull'entità SalesOrder().

Consiglio vivamente lo this free book da Abel Avram e Floyd Marinescu su Domain Drive Design (DDD) perché risponde direttamente alle vostre domande, in una stampa di 100 pagine di dimensioni ridotte. Insieme a come disaccoppiare ulteriormente le entità di dominio in moduli e così via.

Edit: aggiunte più codice

+0

Grazie per la risposta.Non sono tuttavia sicuro di avere un comportamento di una riga d'ordine esposta attraverso i metodi dell'ordine. Sembra che GetOrderLineVatAmount dovrebbe essere sulla linea altrimenti devi passare la linea nella funzione dell'ordine. Inoltre, perché l'ordine dovrebbe avere un ref OrderSepos? –

+0

Tutto dipende da come vedi una "OrderLine". Il codice sopra riflette un approccio "Oggetto di valore". Un oggetto valore è diverso da un'entità nel modo in cui non ha un'identità. Ogni OrderLine ha un'identità? Se sì, lo fa davvero se ci pensi? – eduncan911

+0

OrderLine.LineNumber OrderLine.Description OrderLine.Cost OrderLine.PartNumber La definizione di un valore dell'oggetto qui non è in un'identità; ma, la somma dei suoi valori. Ma come ho notato sopra nella risposta, è possibile esporre IOrderLineRepository() e il client remoto ha i diritti di accesso. – eduncan911

0

Come un follow-up .... ho passato a utilizzare NHibernate (piuttosto che di collegamento a SQL), ma in effetti non avete bisogno dei pronti contro termine per l'OrderLine. Se rimuovi la OrderLine dalla raccolta in Ordine, verrà semplicemente cancellata la OrderLine dal database (presumendo che tu abbia eseguito correttamente la mappatura). Mentre mi sto scambiando con i repository in memoria, se vuoi cercare una particolare riga di ordine (senza conoscere l'ordine genitore) puoi scrivere una linq per bloccare la query che collega l'ordine a orderline dove orderlineid = il valore. In questo modo funziona quando si esegue una query dal db e dalla memoria. Beh, ci sei ...

+0

Bummer, Speravo davvero che qualcuno avesse una risposta migliore alla tua (molto eccellente e molto pertinente!) Domanda, senza dover abbandonare Linq a Sql per farlo! – Funka

1

Dopo aver lottato con questo problema esatto, ho trovato la soluzione. Dopo aver esaminato ciò che il progettista genera con l2sl, mi sono reso conto che la soluzione è nelle associazioni a doppio senso tra ordine e ordine. Un ordine ha molti ordini e un ordine ha un singolo ordine. La soluzione è usare associazioni bidirezionali e un attributo di mappatura chiamato DeleteOnNull (che puoi google per informazioni complete). L'ultima cosa che mi mancava era che la tua classe di entità necessitasse di registrarsi per aggiungere e rimuovere eventi dall'entitàset di l2s. In questi gestori, è necessario impostare l'associazione Ordine sulla riga ordine per essere nullo. Puoi vedere un esempio di questo se guardi un codice generato dal designer di l2s.

So che questo è frustrante, ma dopo giorni di lotta con esso, ho funzionato.