2012-05-13 24 views
5

Przeczytałem między innymi Evansa, Nilssona i McCarthy'ego i rozumiem koncepcje i rozumowanie związane z projektem opartym na domenie; jednak trudno mi je wszystkie połączyć w prawdziwą aplikację. Brak kompletnych przykładów zmusił mnie do drapania się po głowie. Znalazłem wiele frameworków i prostych przykładów, ale nic tak daleko, że naprawdę pokazuje, jak zbudować prawdziwą aplikację biznesową po DDD.Łączenie kropek z DDD

Na przykładzie typowego systemu zarządzania zamówieniami skorzystaj z przypadku anulowania zamówienia. W moim projekcie widzę OrderCancellationService z metodą CancelOrder, która akceptuje numer zamówienia i powód jako parametry. Ma wtedy wykonać następujące „kroki”:

  1. Sprawdź, czy bieżący użytkownik posiada niezbędne uprawnienia do anulowania zamówienia
  2. Odzyskać podmiot zamówienia o określonej kolejności # od OrderRepository
  3. zweryfikować, Zlecenie może zostać anulowane (w przypadku, gdy zlecenie będzie sprawdzać stan Zamówienia w celu oceny zasad lub w przypadku, gdy Zlecenie będzie miało właściwość CanCancel, która obejmuje zasady?)
  4. Zaktualizuj stan podmiotu Zamówienia dzwoniąc do Zamówień.)
  5. Utrzymaj aktualizację d Zamówienie do magazynu danych
  6. Skontaktuj się z CreditCardService przywrócić żadnych opłat kart kredytowych, które zostały już zrealizowane
  7. Dodaj wpis audytu dla operacji

Oczywiście, wszystko to powinno się zdarzyć w transakcji i żadna z operacji nie powinna być dozwolona niezależnie. Mam na myśli to, że muszę anulować transakcję kartą kredytową, jeśli anuluję zamówienie, nie mogę anulować i nie wykonać tego kroku. To, imo, sugeruje lepszą enkapsulację, ale nie chcę mieć zależności od usługi CreditCardService w moim obiekcie domeny (Order), więc wydaje się, że to jest odpowiedzialność domeny usługi.

Szukam kogoś, kto pokaże mi przykłady kodu, jak to może/powinno być "zmontowane". Proces myślowy kryjący się za kodem byłby pomocny w doprowadzeniu mnie do połączenia wszystkich kropek dla siebie. Dzięki!

Odpowiedz

2

Twoja usługa domeny może wyglądać następująco. Zwróć uwagę, że chcemy zachować jak najwięcej logiki w jednostkach, utrzymując niską jakość domeny. Należy również pamiętać, że nie ma bezpośredniej zależności od wdrożenia karty kredytowej lub audytu (DIP). Bazujemy tylko na interfejsach zdefiniowanych w naszym kodzie domeny. Implementacja może zostać później wprowadzona w warstwie aplikacji. Warstwa aplikacji będzie również odpowiedzialna za znajdowanie Zamówienia według numeru i, co ważniejsze, za zawijanie wywołania "Anuluj" w transakcji (wycofywanie się z wyjątków).

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

Dlaczego nie chciałbym "wyszukiwania", zarządzania transakcjami i wywoływania zmian w usłudze? Wydaje się, że za każdym razem zapewniałoby właściwe użycie. – SonOfPirate

+0

Wyszukiwanie - może. Zarządzanie transakcjami nie należy do usługi domeny, zwykle jest realizowane w warstwie aplikacji (wywołującej usługę domeny). "Wezwanie do utrzymywania zmian" jest obsługiwane przez ORM lub UnitOfWork, ponieważ zmieniamy istniejące obiekty, których nie wymaga jawne wywołanie w przypadku NHibernate. Chodzi o to, aby kod domeny był jak najbardziej agnostyczny. – Dmitry

+0

Tak, użyłbym OrderRepository i UoW, aby zachować domenę jako agnostyczną trwałość, jak to możliwe, ale nic nie stoi na przeszkodzie, by kod aplikacji wywoływał Twoją usługę anulowania bez utrzymywania zmian w jednostce Order. Będąc agnostykiem, nie sądziłem, że ma to znaczenie do tej pory, że nie używamy NHibernate, więc wszelkie założenia oparte na tej ORM nie są prawidłowe. – SonOfPirate

2

Nieco inne spojrzenie na to:

//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); 
     } 
    } 
} 

Uwagi:

  • W ogóle widzę tylko potrzebę domeny służby, gdy sprawa komenda/stosowanie wiąże się z zmiana stanu więcej niż jednego agregatu. Na przykład, jeśli potrzebowałbym wywołać metody z agregacji klienta, a także zamówienia, utworzyłbym usługę domeny OrderCancellationService, która wywołała metody w obu agregatach.
  • Warstwa aplikacji orkieruje między infrastrukturą (bramami płatniczymi) a domeną. Podobnie jak obiekty domenowe, usługi domenowe powinny dotyczyć tylko logiki domeny i niewiedzy o infrastrukturze takiej jak bramki płatności; nawet jeśli pobrałeś go za pomocą własnego adaptera.
  • W odniesieniu do uprawnień, użyłbym aspect oriented programming, aby wyodrębnić to z samej logiki. Jak widzisz w moim przykładzie, dodałem atrybut do metody CancelOrder. Możesz użyć interpretera w tej metodzie, aby sprawdzić, czy bieżący użytkownik (który ustawiłbym na Thread.CurrentPrincipal) ma to uprawnienie.
  • Jeśli chodzi o audyt, po prostu powiedziałeś "audyt operacji". Jeśli chodzi tylko o audyt w ogóle (tj. O wszystkie wywołania serwisowe aplikacji), ponownie użyłbym przechwytywaczy w metodzie, logując użytkownika, która metoda została wywołana, i jakie parametry. Jeśli jednak chodziło ci o audyt w celu anulowania zamówień/płatności, zrób coś podobnego do przykładu Dmitry'ego.
+0

+1 czyste rozróżnienie między aplikacją a domeną –

+1

co z przydziałem użytkownika dla danego zasobu (orderId)? –

+0

dla uprawnień względem zasobów danych w przeciwieństwie do funkcji/transakcji, które traktowałbym jak regułę biznesową i przeprowadzania kontroli w kodzie, tak jak każdej innej reguły –