用DDD连接点

时间:2012-05-13 20:02:39

标签: domain-driven-design ddd-repositories ddd-service

我已经阅读过Evans,Nilsson和McCarthy等人,并了解域驱动设计背后的概念和推理;但是,我发现很难将所有这些放在一个真实世界的应用程序中。缺乏完整的例子让我摸不着头脑。我发现了很多框架和简单的例子,但到目前为止还没有真正演示如何在DDD之后构建真正的业务应用程序。

以典型的订单管理系统为例,以订单取消为例。在我的设计中,我可以看到带有CancelOrder方法的OrderCancellationService,该方法接受订单#和作为参数的原因。然后它必须执行以下“步骤”:

  1. 确认当前用户具有取消订单的必要权限
  2. 使用OrderRepository
  3. 中指定的订单#检索Order实体
  4. 验证订单是否可能被取消(如果服务询问订单的状态以评估规则,或订单是否具有封装规则的CanCancel属性?)
  5. 通过调用Order.Cancel(reason)
  6. 更新Order实体的状态
  7. 将更新后的订单保留到数据存储
  8. 联系CreditCardService以还原已经处理的任何信用卡费用
  9. 为操作添加审核条目
  10. 当然,所有这些都应该在事务中发生,并且不允许任何操作独立发生。我的意思是,如果我取消订单,我必须还原信用卡交易,我无法取消而不执行此步骤。这个,imo,建议更好的封装,但我不想依赖于我的域对象(Order)中的CreditCardService,所以看起来这是域服务的责任。

    我正在寻找有人向我展示代码示例如何/应该“组装”。代码背后的思考过程将有助于我为自己连接所有的点。 THX!

2 个答案:

答案 0 :(得分: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);
    }
}

答案 1 :(得分: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);
        }
    }
}

注意:

  • 一般情况下,当命令/用例涉及多个聚合的状态更改时,我只看到需要服务。例如,如果我需要在Customer聚合和Order上调用方法,那么我将创建调用两个聚合上的方法的域服务OrderCancellationService。
  • 应用程序层在基础架构(支付网关)和域之间进行协调。与域对象一样,域服务应该只关注域逻辑,而不关心支付网关等基础设施;即使你使用自己的适配器抽象它。
  • 关于权限,我会使用aspect oriented programming将其从逻辑本身中提取出来。正如您在我的示例中看到的,我已经为CancelOrder方法添加了一个属性。您可以在该方法上使用截取器来查看当前用户(我将在 Thread.CurrentPrincipal 上设置)是否具有该权限。
  • 关于审计,您只需说“审计操作”。如果你只是一般意味着审计(即对于所有app服务调用),我将再次使用方法上的拦截器,记录用户,调用哪个方法以及使用什么参数。但是,如果您的意思是专门针对取消订单/付款进行审核,那么请执行类似于Dmitry的示例。