2012-05-13 8 views
5

저는 Evans, Nilsson 및 McCarthy를 읽었으며 도메인 중심 디자인의 개념과 추론을 이해합니다. 그러나이 모든 것을 실제 응용 프로그램에 넣기 란 어렵습니다. 완전한 예가 부족하여 내 머리가 긁히게되었습니다. 나는 많은 프레임 워크와 간단한 예제를 발견했으나 DDD 다음에 실제 비즈니스 애플리케이션을 구축하는 방법을 실제로 보여주었습니다.점을 DDD로 연결

일반적인 주문 관리 시스템을 예로 들어 주문 취소의 경우를 생각해보십시오. 내 디자인에서는 CancelOrder 메서드를 사용하여 OrderCancellationService를 볼 수 있습니다.이 메서드는 매개 변수로 주문 번호와 이유를 허용합니다.

  1. 현재 사용자가 주문
  2. OrderRepository
  3. 있는지 확인에서 지정된 순서 번호와 주문 엔티티를 검색을 취소하는 데 필요한 권한이 있는지 확인합니다 : 그것은 다음 '단계'를 수행 할 수있다 주문이 취소 될 수 있습니다 (서비스가 규칙을 평가할 수있는 주문 상태를 문의하거나 주문에 규칙을 캡슐화하는 CanCancel 속성이 있어야합니까?)
  4. Order.Cancel을 호출하여 Order 엔터티의 상태를 업데이트합니다 (이유)
  5. 업데이트 유지 데이터 저장소
  6. 연락 이미
  7. 조작에 대한 감사 항목을 추가 처리 된 모든 신용 카드로 지불을 되 돌리는 CreditCardService에 D 주문은
  8. 물론

,이 모든 거래에서 발생한다 독립적으로 발생할 수있는 조작이 있어서는 안됩니다. 주문을 취소하고 취소 할 수 없으며이 단계를 수행하지 않으면 신용 카드 거래를 되돌려 야합니다. 이것은 imo가 더 나은 캡슐화를 제안하지만 내 도메인 객체 (Order)의 CreditCardService에 의존하지 않으므로 도메인 서비스의 책임이라고 생각됩니다.

나는 어떻게 이것이 "조립"될 수 있는지/또는 있어야하는지 코드 예제를 보여줄 누군가를 찾고 있습니다. 코드 뒤의 생각 과정은 나 자신을 위해 모든 점들을 연결시키는 데 도움이 될 것입니다. 고마워!

답변

2

도메인 서비스는 이와 같이 보일 수 있습니다. 엔티티에서 최대한 많은 논리를 유지하여 도메인 서비스를 가늘게 유지하려고합니다. 또한 신용 카드 또는 감사인 구현 (DIP)에 직접적인 종속성이 없음을 유의하십시오. 우리는 도메인 코드에 정의 된 인터페이스에만 의존합니다. 구현은 나중에 응용 프로그램 계층에 삽입 할 수 있습니다. 응용 프로그램 계층은 번호순으로 주문을 찾는 책임이 있으며 더 중요한 것은 트랜잭션에서 '취소'호출을 래핑하는 것입니다 (예외시 롤백). 거기에

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

서비스 내에서 '조회', 트랜잭션 관리 및 전화 변경이 필요하지 않은 이유는 무엇입니까? 매번 올바른 사용법을 보장하는 것 같습니다. – SonOfPirate

+0

조회 - 아마도. 트랜잭션 관리는 도메인 서비스에 속하지 않으며 일반적으로 응용 프로그램 계층 (도메인 서비스 호출자)에서 구현됩니다. '변경을 지속하라는 호출'은 ORM이나 UnitOfWork에 의해 처리됩니다. 왜냐하면 우리가 기존의 객체를 변경하고 있기 때문에 NHibernate의 경우 명시적인 호출이 필요하지 않습니다. 아이디어는 도메인 코드를 가능한 한 영원 불가지론 자로 유지하는 것입니다. – Dmitry

+0

그래요, 나는 OrderRepository와 UoW를 사용하여 가능한 한 영속성을 불가지론하는 것으로 도메인을 유지할 것이지만 Order 엔티티의 변경 사항을 유지하지 않고 응용 프로그램 코드가 취소 서비스를 호출하는 것을 막을 수는 없습니다. 불가지론 자이기 때문에 지금까지 우리가 NHibernate를 사용하지 않는다는 것이 중요하지 않았다고 생각했기 때문에 ORM을 기반으로하는 가정은 유효하지 않습니다. – SonOfPirate

2

약간 다른 의견 :

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

주 : 명령/사용 사례가를 포함 할 때 일반적으로

  • 나는 단지 도메인 서비스에 대한 필요성을 둘 이상의 집계의 상태 변경. 예를 들어 Order뿐 아니라 Customer 집계에서 메소드를 호출해야한다면 두 집계에서 메소드를 호출 한 OrderCancellationService 도메인 서비스를 생성합니다.
  • 응용 프로그램 계층은 인프라 (지불 게이트웨이)와 도메인 사이를 조정합니다. 도메인 개체와 마찬가지로 도메인 서비스는 도메인 논리에만 관심을 가져야하며 지불 게이트웨이와 같은 인프라는 알지 못합니다. 자신의 어댑터를 사용하여 추상화 한 경우에도 마찬가지입니다.
  • 권한과 관련하여 로직 (Logic) 자체에서 이것을 추출하려면 aspect oriented programming을 사용합니다. 예제에서 보았 듯이 CancelOrder 메서드에 특성을 추가했습니다. 해당 메서드에 대한 인터셉터를 사용하여 현재 사용자 (내가 설정할 경우 Thread.CurrentPrincipal)에 해당 권한이 있는지 확인할 수 있습니다.
  • 감사와 관련하여 단순히 '운영 감사'라고 말한 것입니다. 즉, 모든 앱 서비스 호출에 대한 일반적인 감사를 의미하는 경우, 메소드에 대한 인터셉터를 사용하고, 사용자를 기록하고, 호출 된 메소드와 매개 변수를 사용합니다. 그러나 주문/지불 취소를 위해 특별히 감사한다는 의미라면 드미트리의 예와 비슷한 것을하십시오.
+0

+1 응용 프로그램과 도메인 간의 명확한 구별 –

+1

특정 리소스 (orderId)의 userpermission은 어떻게됩니까? –

+0

데이터 리소스에 대한 사용 권한을 함수/트랜잭션과 대조적으로 비즈니스 규칙처럼 취급하고 다른 규칙과 마찬가지로 코드를 검사합니다 –