2012-05-13 13 views
5

Ho letto Evans, Nilsson e McCarthy, tra gli altri, e ho compreso i concetti e il ragionamento dietro un design guidato dal dominio; tuttavia, trovo difficile mettere insieme tutti questi elementi in un'applicazione reale. La mancanza di esempi completi mi ha lasciato grattarmi la testa. Ho trovato molti framework e semplici esempi, ma finora non c'è nulla che mostri davvero come costruire una vera applicazione aziendale seguendo un DDD.Collegamento dei punti con DDD

Utilizzando il tipico sistema di gestione degli ordini come esempio, prendere in considerazione la cancellazione dell'ordine. Nella mia progettazione posso vedere un OrderCancellationService con un metodo CancelOrder che accetta l'ordine # e un motivo come parametri. Ha poi per eseguire le seguenti 'passi':

  1. verificare che l'utente corrente ha l'autorizzazione necessaria per annullare un ordine
  2. recuperare l'entità dell'Ordine con l'ordine specificato # dal OrderRepository
  3. Verificare che l'Ordine può essere annullato (il servizio dovrebbe interrogare lo stato dell'Ordine per valutare le regole o l'Ordine deve possedere una proprietà CanCancel che incapsula le regole?)
  4. Aggiornare lo stato dell'entità dell'Ordine chiamando Order.Cancel (motivo
  5. Persistenza dell'aggiornamento d Ordine al archivio dati
  6. Contattare la CreditCardService per ripristinare eventuali spese di carta di credito che sono già stati elaborati
  7. Aggiungi una voce di controllo per l'operazione

Naturalmente, tutto questo dovrebbe accadere in una transazione e nessuna delle operazioni dovrebbe essere autorizzata a verificarsi in modo indipendente. Quello che voglio dire è che devo annullare la transazione con la carta di credito se annullo l'ordine, non posso annullare e non eseguire questo passaggio. Questo, imo, suggerisce un migliore incapsulamento ma non voglio avere una dipendenza dal CreditCardService nel mio oggetto dominio (Ordine), quindi sembra che questa sia la responsabilità del servizio di dominio.

Sto cercando qualcuno che mi mostri esempi di codice su come questo potrebbe/dovrebbe essere "assemblato". Il processo di riflessione dietro il codice sarebbe utile per farmi collegare tutti i punti per me stesso. Grazie!

risposta

2

Il tuo servizio di dominio potrebbe essere simile a questo. Si noti che vogliamo mantenere più logica possibile nelle entità, mantenendo il servizio di dominio sottile. Si noti inoltre che non vi è alcuna dipendenza diretta dall'implementazione della carta di credito o dell'auditor (DIP). Dipenderemo solo dalle interfacce definite nel nostro codice di dominio. L'implementazione può essere successivamente iniettata nel livello dell'applicazione. Il livello di applicazione dovrebbe anche essere responsabile della ricerca dell'ordine per numero e, soprattutto, dell'avvolgimento della chiamata "Annulla" in una transazione (ripristino delle eccezioni).

class OrderCancellationService { 

    private readonly ICreditCardGateway _creditCardGateway; 
    private readonly IAuditor _auditor; 

    public OrderCancellationService(
     ICreditCardGateway creditCardGateway, 
     IAuditor auditor) { 
     if (creditCardGateway == null) { 
      throw new ArgumentNullException("creditCardGateway"); 
     } 
     if (auditor == null) { 
      throw new ArgumentNullException("auditor"); 
     } 
     _creditCardGateway = creditCardGateway; 
     _auditor = auditor; 
    } 

    public void Cancel(Order order) { 
     if (order == null) { 
      throw new ArgumentNullException("order"); 
     } 
     // get current user through Ambient Context: 
     // http://blogs.msdn.com/b/ploeh/archive/2007/07/23/ambientcontext.aspx 
     if (!CurrentUser.CanCancelOrders()) { 
      throw new InvalidOperationException(
       "Not enough permissions to cancel order. Use 'CanCancelOrders' to check."); 
     } 
     // try to keep as much domain logic in entities as possible 
     if(!order.CanBeCancelled()) { 
      throw new ArgumentException(
       "Order can not be cancelled. Use 'CanBeCancelled' to check."); 
     } 
     order.Cancel(); 

     // this can throw GatewayException that would be caught by the 
     // 'Cancel' caller and rollback the transaction 
     _creditCardGateway.RevertChargesFor(order); 

     _auditor.AuditCancellationFor(order); 
    } 
} 
+0

Perché non desidero che la "ricerca", la gestione delle transazioni e la chiamata mantengano i cambiamenti all'interno del servizio? Sembra che garantirebbe un uso corretto ogni volta. Ricerca – SonOfPirate

+0

- forse. La gestione delle transazioni non appartiene al servizio di dominio, di solito è implementata a livello di applicazione (chiamante del servizio di dominio). "Call to persist changes" è gestito da ORM o UnitOfWork poiché stiamo modificando gli oggetti esistenti, nessuna chiamata esplicita è necessaria nel caso di NHibernate. L'idea è di mantenere il codice del dominio come ostinente alla persistenza possibile. – Dmitry

+0

Sì, utilizzerei un OrderRepository e UoW per mantenere il dominio come indipendente dalla persistenza possibile, ma nulla impedisce al codice dell'applicazione di chiamare il servizio di cancellazione senza mantenere le modifiche all'entità Ordine. Essendo agnostico, non pensavo che fosse importante fino ad ora che non usassimo NHibernate, quindi qualsiasi ipotesi basata su quell'ORM non è valida. – SonOfPirate

2

Un introito leggermente diverso su di esso:

//UI 
public class OrderController 
{ 
    private readonly IApplicationService _applicationService; 

    [HttpPost] 
    public ActionResult CancelOrder(CancelOrderViewModel viewModel) 
    { 
     _applicationService.CancelOrder(new CancelOrderCommand 
     { 
      OrderId = viewModel.OrderId, 
      UserChangedTheirMind = viewModel.UserChangedTheirMind, 
      UserFoundItemCheaperElsewhere = viewModel.UserFoundItemCheaperElsewhere 
     }); 

     return RedirectToAction("CancelledSucessfully"); 
    } 
} 

//App Service 
public class ApplicationService : IApplicationService 
{ 
    private readonly IOrderRepository _orderRepository; 
    private readonly IPaymentGateway _paymentGateway; 

    //provided by DI 
    public ApplicationService(IOrderRepository orderRepository, IPaymentGateway paymentGateway) 
    { 
     _orderRepository = orderRepository; 
     _paymentGateway = paymentGateway; 
    } 

    [RequiredPermission(PermissionNames.CancelOrder)] 
    public void CancelOrder(CancelOrderCommand command) 
    { 
     using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create()) 
     { 
      Order order = _orderRepository.GetById(command.OrderId); 

      if (!order.CanBeCancelled()) 
       throw new InvalidOperationException("The order cannot be cancelled"); 

      if (command.UserChangedTheirMind) 
       order.Cancel(CancellationReason.UserChangeTheirMind); 
      if (command.UserFoundItemCheaperElsewhere) 
       order.Cancel(CancellationReason.UserFoundItemCheaperElsewhere); 

      _orderRepository.Save(order); 

      _paymentGateway.RevertCharges(order.PaymentAuthorisationCode, order.Amount); 
     } 
    } 
} 

Note:

  • In generale vedo solo la necessità di un dominio servizio quando un caso di comando/utilizzo comporta la modifica dello stato di più di un aggregato. Ad esempio, se dovessi invocare metodi sull'aggregazione del cliente e sull'ordine, creerei il servizio dominio OrderCancellationService che invoca i metodi su entrambi gli aggregati.
  • Il livello applicazione orchestra tra l'infrastruttura (gateway di pagamento) e il dominio. Come gli oggetti di dominio, i servizi di dominio dovrebbero riguardare solo la logica del dominio e ignorare infrastrutture come i gateway di pagamento; anche se lo hai estratto usando il tuo adattatore.
  • Per quanto riguarda le autorizzazioni, vorrei utilizzare aspect oriented programming per estrarre questo dalla logica stessa. Come vedi nel mio esempio, ho aggiunto un attributo al metodo CancelOrder. È possibile utilizzare un'intercettatrice su tale metodo per verificare se l'utente corrente (che imposterò su Thread.CurrentPrincipal) disponga di tale autorizzazione.
  • Per quanto riguarda l'auditing, hai semplicemente detto "audit per l'operazione". Se intendi semplicemente il controllo in generale (ad esempio per tutte le chiamate al servizio app), utilizzerei nuovamente gli interceptor sul metodo, registrando l'utente, con quale metodo è stato chiamato e con quali parametri. Se tuttavia volevi verificare specificamente la cancellazione di ordini/pagamenti, allora fai qualcosa di simile all'esempio di Dmitry.
+0

+1 distinzione netta tra applicazione e dominio –

+1

e sulla userpermission di quella particolare risorsa (orderId)? –

+0

per le autorizzazioni contro le risorse dati rispetto alle funzioni/transazioni che tratterò come una regola aziendale e fare i controlli nel codice come qualsiasi altra regola –