2012-05-13 34 views
5

Tôi đã đọc Evans, Nilsson và McCarthy, trong số những người khác, và hiểu các khái niệm và lý do đằng sau thiết kế định hướng miền; tuy nhiên, tôi thấy rất khó để kết hợp tất cả chúng lại với nhau trong một ứng dụng thực tế. Việc thiếu các ví dụ hoàn chỉnh đã khiến tôi gãi đầu. Tôi đã tìm thấy rất nhiều khuôn khổ và ví dụ đơn giản nhưng không có gì cho đến nay mà thực sự chứng minh làm thế nào để xây dựng một ứng dụng kinh doanh thực tế sau một DDD.Kết nối các dấu chấm với DDD

Sử dụng hệ thống quản lý đơn đặt hàng điển hình làm ví dụ, lấy trường hợp hủy đơn đặt hàng. Trong thiết kế của tôi, tôi có thể thấy một OrderCancellationService với một phương thức CancelOrder chấp nhận thứ tự # và một lý do làm tham số. Sau đó nó có để thực hiện 'bước' sau:

  1. Xác minh rằng người dùng hiện có sự cho phép cần thiết để hủy bỏ Lệnh
  2. Lấy thực thể Order trình tự quy định # từ OrderRepository
  3. Xác minh rằng Lệnh có thể bị hủy bỏ (liệu dịch vụ có nên thẩm vấn tình trạng của Lệnh để đánh giá các quy tắc hay Lệnh có tài sản CanCancel đóng gói các quy tắc?)
  4. Cập nhật trạng thái Đơn hàng bằng cách gọi Order.Cancel (lý do)
  5. Tiếp tục cập nhật d theo thứ tự để lưu trữ dữ liệu
  6. Liên hệ với CreditCardService để trở lại bất kỳ khoản phí thẻ tín dụng đã được xử lý
  7. Thêm một mục kiểm toán cho các hoạt động

Tất nhiên, tất cả điều này nên xảy ra trong một giao dịch và không có hoạt động nào được phép xảy ra độc lập. Ý tôi là, tôi phải hoàn nguyên giao dịch thẻ tín dụng nếu tôi hủy đơn đặt hàng, tôi không thể hủy và không thực hiện bước này. Điều này, imo, cho thấy đóng gói tốt hơn nhưng tôi không muốn có một sự phụ thuộc vào CreditCardService trong đối tượng miền của tôi (Order), do đó, có vẻ như đây là trách nhiệm của dịch vụ miền.

Tôi đang tìm ai đó để hiển thị cho tôi các ví dụ mã về cách thức này có thể/nên được "lắp ráp". Quá trình suy nghĩ đằng sau mã sẽ hữu ích trong việc giúp tôi kết nối tất cả các dấu chấm cho bản thân mình. Cám ơn!

Trả lời

2

Dịch vụ miền của bạn có thể trông giống như thế này. Lưu ý rằng chúng tôi muốn giữ càng nhiều logic càng tốt trong các thực thể, giữ cho dịch vụ miền trở nên mỏng. Cũng lưu ý rằng không có sự phụ thuộc trực tiếp vào việc thực hiện kiểm tra thẻ tín dụng hoặc kiểm toán (DIP). Chúng tôi chỉ phụ thuộc vào các giao diện được xác định trong mã miền của chúng tôi. Việc thực hiện sau này có thể được tiêm trong lớp ứng dụng. Lớp ứng dụng cũng sẽ chịu trách nhiệm tìm kiếm thứ tự theo số và, quan trọng hơn, để gói lệnh 'Hủy' trong một giao dịch (lùi lại trên các ngoại lệ).

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

Tại sao tôi không muốn 'tra cứu', quản lý giao dịch và gọi để thay đổi liên tục trong dịch vụ? Có vẻ như sẽ đảm bảo việc sử dụng phù hợp mọi lúc. – SonOfPirate

+0

Tra cứu - có thể. Quản lý giao dịch không thuộc về dịch vụ miền, nó thường được thực hiện ở tầng ứng dụng (người gọi của dịch vụ miền). 'Call to persist changes' được xử lý bởi ORM hoặc UnitOfWork vì chúng ta đang thay đổi các đối tượng hiện có mà không cần gọi rõ ràng trong trường hợp NHibernate. Ý tưởng là giữ mã miền là thuyết bất khả tri bền vững nhất có thể. – Dmitry

+0

Vâng, tôi sẽ sử dụng một OrderRepository và UoW để giữ cho miền này trở nên bất khả tri nhất có thể, nhưng không có gì ngăn cản mã ứng dụng gọi dịch vụ hủy của bạn mà không có sự thay đổi đối với thực thể Đơn hàng. Là không thuyết phục, tôi đã không nghĩ rằng nó quan trọng cho đến bây giờ mà chúng tôi không sử dụng NHibernate, do đó, bất kỳ giả định dựa trên ORM đó là không hợp lệ. – SonOfPirate

2

Một mất một chút khác nhau về nó:

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

Ghi chú:

  • Nói chung tôi chỉ nhìn thấy sự cần thiết của một dịch vụ miền khi một trường hợp lệnh/sử dụng liên quan đến việc thay đổi trạng thái của nhiều hơn một tổng hợp. Ví dụ, nếu tôi cần gọi các phương thức trên Customer aggregate cũng như Order, thì tôi sẽ tạo ra dịch vụ miền OrderCancellationService để gọi các phương thức trên cả hai aggregates.
  • Lớp ứng dụng phối hợp giữa cơ sở hạ tầng (cổng thanh toán) và miền. Giống như các đối tượng miền, các dịch vụ miền chỉ nên được quan tâm đến logic miền và không biết về cơ sở hạ tầng như cổng thanh toán; ngay cả khi bạn đã tóm tắt nó bằng cách sử dụng bộ điều hợp của riêng bạn.
  • Liên quan đến quyền, tôi sẽ sử dụng aspect oriented programming để trích xuất nội dung này khỏi chính logic đó. Như bạn thấy trong ví dụ của tôi, tôi đã thêm một thuộc tính vào phương thức CancelOrder. Bạn có thể sử dụng một intercepter trên phương pháp đó để xem người dùng hiện tại (mà tôi sẽ đặt trên Thread.CurrentPrincipal) có quyền đó.
  • Đối với kiểm toán, bạn chỉ cần nói 'kiểm toán cho hoạt động'. Nếu bạn chỉ có nghĩa là kiểm toán nói chung, (nghĩa là đối với tất cả các cuộc gọi dịch vụ ứng dụng), một lần nữa tôi sẽ sử dụng các trình chặn trên phương thức, ghi nhật ký người dùng, phương thức nào được gọi và với tham số nào. Tuy nhiên, nếu bạn có nghĩa là kiểm toán cụ thể cho việc hủy bỏ các đơn đặt hàng/thanh toán sau đó làm một cái gì đó tương tự như ví dụ của Dmitry.
+0

+1 phân biệt rõ ràng giữa ứng dụng và tên miền –

+1

điều gì về userpermission của tài nguyên cụ thể đó (orderId)? –

+0

cho các quyền đối với tài nguyên dữ liệu trái với các chức năng/giao dịch tôi sẽ coi như quy tắc kinh doanh và thực hiện kiểm tra trong mã giống như bất kỳ quy tắc nào khác –

Các vấn đề liên quan