Forzare l'accesso secondario di root aggregato attraverso la sola radice aggregata

4

Contesto

Sto sviluppando un'applicazione che usa un approccio basato sul Domain Driven Design. Voglio utilizzare un modello di design laddove appropriato e applicare tutti i principi SOLID .

Scenario

Ho un ordine e voglio consentire ai clienti di aggiungere righe di ordini ad esso.

Considera il seguente pseudo codice:

class Order
    OrderLines[] (readonly)
    AddOrderLine(orderLine)

class OrderLine
    Product
    Amount

class OrderLineFactory
    OrderLine Create(product, amount)

class OrderLineRepository
    Save(orderLine)

Ora consente di aggiungere una OrderLine all'Ordine:

Order.AddOrderLine(OrderLineFactory.Create(product: "Chocolate Cake", amount: 3))

A proposito, OrderLineFactory è lì perché, nel mio caso, c'è una logica relativamente complessa nella creazione delle linee d'ordine. Questo è uno dei motivi principali per applicare il modello di fabbrica.

problema

Il problema è che potrei farlo invece ...

orderLine = OrderLineFactory.Create(product: "Chocolate Cake", amount: 3)
OrderLineRepository.Save(orderLine)

... e aggirare il metodo Order.AddOrderLine , abbreviando efficacemente tutti i controlli che vengono eseguiti in quel metodo (ad esempio proteggendo contro i duplicati o limitando la quantità dell'ordine).

Lavorando verso una soluzione

Un argomento potrebbe essere che non ci dovrebbe essere un metodo Save su OrderLineRepository , perché OrderLine non è una radice aggregata. Questo (proprio come voglio) renderà il cliente incapace di memorizzare la OrderLine nuda se non aggiungendola all'Ordine e salvando l'Ordine di conseguenza.

Tuttavia, il fatto di avere un metodo Save su repository che riguardano gli aggregati non root, consente di salvare direttamente le modifiche apportate a loro, come ad esempio:

orderLine = orderLineRepository.Get(31923)
orderLine.Amount = 5
OrderLineRepository.Save(orderLine)

Non essere in grado di salvare OrderLine mi richiederebbe di fare qualcosa di simile ...

order = OrderRepository.FindByOrderLineId(31923)
order.OrderLines[31923].Amount = 5
OrderRepository.Save(order)

... che non è poi così efficiente o pratico. Potrei aggiungere un requisito per fornire l'ID ordine (effettivamente ridondante), ma ciò comporterebbe un onere inutile sul client.

Quindi diciamo che voglio seguire il mio metodo OrderLineRepository.Save e trovare ancora un modo per impedire al mio client di essere in grado di aggirare il mio metodo Order.AddOrderLine .

Tutto ciò a cui riesco a pensare ora è l'eliminazione di OrderLineFactory e la creazione di un servizio di dominio che combina la creazione e l'assegnazione.

class OrderService
    AddOrderLine(order, product, amount)

Quindi possiamo:

order = orderRepository.Get(123)
OrderService.AddOrderLine(order: order, product: "Chocolate Cake", amount: 3))
orderRepository.Save(order)

Non mi piace molto il servizio che combina più problemi qui, però, quelli di creazione e assegnazione. Ciò richiederebbe ... una fabbrica ... di nuovo.

Ok, consideriamo una fabbrica privata, una che non è disponibile per il client ma solo per il servizio di dominio. Nel mio caso mi sarei bloccato con le unit test e Inversion Of Control. Creerei una dipendenza innescabile, dal momento che il mio cliente non sarebbe in grado di registrare il mio stabilimento non pubblico.

Riepilogo

  • Voglio che il mio cliente sia in grado di creare righe d'ordine attraverso la classe Order e in nessun altro modo
  • Non voglio estrapolare più problemi in una singola classe
  • Voglio mantenere la creazione OrderLine al di fuori della classe Order
  • Mi piacerebbe continuare a utilizzare una fabbrica per questo

Quindi sono un po 'bloccato qui, cercando di trovare il modo migliore per affrontarlo. Qualche suggerimento?

    
posta Sandor Drieënhuizen 25.02.2012 - 14:50
fonte

2 risposte

3

Perché non rendere l'ordine responsabile della creazione di ordini?

La funzione AddOrderline potrebbe prendere i parametri Product e Amount e creare una Orderline cambiando un ordine sarebbe dal prodotto su un ordine in modo da avere una funzione ChangeOrderLine (string productName, int amount) e DeleteOrderLine (string productName)

Fatto e fatto.

    
risposta data 25.02.2012 - 17:26
fonte
1

Mi sento come se fossi molto vicino, ma mi sono perso

order = orderRepository.Get(123)
OrderService.AddOrderLine(order: order, product: "Chocolate Cake", amount: 3))
orderRepository.Save(order)

Quello che stavi cercando è più simile a questo:

order = orderRepository.Get(123)
order.AddOrderLine(orderService: orderService, product: "Chocolate Cake", amount: 3))
orderRepository.Save(order)

I servizi di dominio supportano le query all'interno del modello di dominio - non scrivono modifiche al modello; gli aggregati cambiano e proteggono lo stato del dominio. Quindi, di regola, si desidera passare il servizio di dominio all'aggregato, quindi lasciare che l'aggregato passi lo stato corrente al servizio, secondo necessità. Ad esempio

// Order.AddOrderLine()
orderLine = orderService.createOrderLine(this.id, product, amount);
this.lines.add(orderLine);

Nota la (implicita) separazione delle responsabilità

  • Il servizio di dominio crea un'istanza di orderLine, utilizzando solo il contesto fornito. Ma non c'è persistenza qui, sono solo dati transitori in memoria a questo punto.
  • L'aggregato quindi valuta la riga ordine per determinare se soddisfa o meno l'invariante. In tal caso, orderLine diventerà parte del grafico delle entità raggiungibili tramite la radice aggregata.

However, having a Save method on repositories that concern non-root aggregates, allows for saving changes made to them directly

Questa non è una buona cosa?

orderLine = orderLineRepository.Get(31923)
orderLine.Amount = 5
OrderLineRepository.Save(orderLine)

Ma se questo orderLine fa parte di un aggregato di ordini, presumibilmente l'Ordine dovrebbe controllare che lo stato dell'ordine sia coerente. Non può farlo se insisti a essere in grado di mutare direttamente le entità subordinate.

Parte del punto di aggregazione è che tutte le modifiche al modello di dominio devono passare attraverso i percorsi che costringono il modello a rimanere coerenti. Tutti i metodi OrderLine riporteranno il grafico all'ordine per garantire che l'invariante sia ancora soddisfatto?

Naturalmente, questo potrebbe effettivamente essere un suggerimento che i confini aggregati sono nel posto sbagliato. Se dovessi essere in grado di modificare OrderLine senza l'intero Ordine, allora forse OrderLines sono radici aggregate e non solo entità subordinate. I tuoi esperti di dominio potrebbero dirti che le discrepanze tra Order e OrderLines non sono in realtà particolarmente costose; assicurarsi che siano rari (piuttosto che eliminarli completamente) può essere sufficiente. Cavalli per i corsi.

    
risposta data 03.05.2016 - 07:03
fonte

Leggi altre domande sui tag