4

我目前正在使用 .net 和 nhibernate 为旅游行业的客户开发一些相当大的应用程序,并且在实施 DDD 时遇到了一些问题,并且团队内部在最佳方式上存在分歧。我希望有人可以提供一些指导。

目前,我们已经在域外实现了一个服务层,每个聚合根([EntityName]Service)都有一个服务。所有其他层使用这些服务通过 GetByThis() 和 GetByTheOther() 等方法获取对聚合根的引用。我们从其他层对域的所有调用都是通过这些服务进行的。

这些服务持有对存储库的注入引用(在其他任何地方都没有引用),并且还负责所有保存/更新行为和管理事务性。服务方法越来越复杂,有时具有似乎属于域的行为,例如条件创建逻辑(如果 property = this 将子对象设置为某物,否则为其他东西)。我们的域实体大多具有简单的方法,例如 GetByThis() 和 HasAThing()。我觉得我们正在失去我们领域的表现力。

我的主要问题是:

  • 服务层应该包含这么多逻辑吗?如果不是,它应该去哪里?如果是域,聚合根是否应该包含对存储库的引用?如果是,如何将这些注入(注入创建聚合根的工厂?)
  • 应如何处理事务性?
  • 实体(或聚合根)是否应该持有对域服务的引用?如果是这样,他们应该如何获得参考资料?
  • 为了获得实体的新 id,我们必须调用一个存储过程,该过程已包装在存储库中。你会在哪里引用这个?需要创建许多子实体的实体上的一些复杂方法需要引用这个?

编辑

感谢@david-masters 和@guillaume31 的深思熟虑的回答。

你帮助我解决了我得到的那种“臭代码”的感觉。

首先,我应该说我们有一个(非常)遗留的 oracle DB 来应对,因此需要 Id Generation(以及其他问题)。

对于其他看到这个的人来说,这两个答案都给出了很好的建议,但对我来说,这是最好的建议:

“从务实的角度来看,这就是我会问自己的问题:如果我想在另一个应用程序中使用我的域层的一部分并重用它,它是否包含我在新应用程序中利用域所需的所有业务规则和行为? 如果不是,那么可能意味着当前在应用程序端的一些部分需要移动到领域层。”</p>

考虑到这一点,我重新评估了我们的域和服务层,现在相信我已经解决了我们的设计问题

4

2 回答 2

12

应用程序服务层不应包含域逻辑。应用程序服务的目的是“协调”。它不应该做出任何领域决定;所有业务决策都应该在域对象或域服务中。应用程序服务接收来自消费者(通常是 UI)的调用,并调用域和基础设施服务中的方法。应用程序服务不应该像您所描述的那样具有杂乱无章的名称。他们应该有描述用例的有意义的动词。以下是银行应用程序的应用程序服务的示例:

public class AccountService : IAccountService
{
    //These are injected via dependency injection on the constructor
    private readonly IAccountRepository _accountRespository;
    private readonly IEmailNotificationService _emailNotificationServce;

    public void FreezeAccount(Guid accountId)
    {
        Account account = _accountRespository.GetById(accountId);

        using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
        {
             account.Freeze();
             _accountRespository.Save(account);  
             _emailNotificationServce.Send(CreateFreezeNotification(account));           
        }
    }
}

我建议您的实体/聚合不要引用存储库,因此根本没有依赖关系。如果一个聚合需要来自第二个聚合的信息来做出决定,应用程序服务应该从它的存储库中获取第二个聚合并通过一个方法将它传递给第一个聚合。

我会将相同的原则应用于域服务。如果需要域服务(通常用例需要在一个事务中涉及多个聚合(尽管您应该尝试通过更好地设计聚合来避免这种情况以减少并发问题)),那么应用程序服务应该首先获取所需的聚合,然后将它们传递给域服务。然后域服务可以调用聚合上的域逻辑。

事务应在此应用程序服务级别处理。正如您在上面看到的,所有被调用和持久化的逻辑都包装在一个 UnitOfWork 中。只有当这个块没有错误地完成时,事务才会完成。

关于 ID:我总是选择 Guid 而不是数据库 ID。我只是发现生活变得如此轻松,并且避免了您描述的问题。如果您的数据库需要负责 ID(例如 INT IDENTITY 列),那么也许您可以将其设为辅助 ID 属性,并将 Guid ID 用于域目的以节省开销?

于 2012-06-28T10:20:30.213 回答
5

服务层应该包含这么多逻辑吗?如果不是,它应该去哪里?

我假设您在这里谈论的是应用程序层中的服务,而不是域层。似乎您的域对象几乎是贫乏的,有些人认为这是一种反模式,但对此存在很多争论。

从务实的角度来看,这就是我会问自己的问题:如果我想将我的域层的一部分并在另一个应用程序中重用它,它是否包含我在新应用程序中利用域所需的所有业务规则和行为?如果不是,那么可能意味着当前在应用程序端的某些部分需要移动到域层。

请注意,我在这里谈论的是纯粹的领域业务规则,而不是特定于应用程序的规则。例如,必须通过 4 个步骤的向导执行某些操作,在最后一步结束时要求用户确认,以及在最后一步应用后所有修改都刷新到持久存储中。 - 特定的业务规则,而不是域规则。因此它们不应该被移动到域层。

如果是域,聚合根是否应该包含对存储库的引用?

IMO 聚合根不应该持有对其自己存储库的引用并知道如何存储自己,因为它打破了持久性无知并在域对象中引入了额外的责任,这将混淆它。但是,聚合根有时可能包含对另一个实体的存储库的引用。

应如何处理事务性?

我想说有两种交易类型:

  • 应用层中的“用户”事务,也就是“工作单元”。这些是跨越用例或在 Web 应用程序中的网页生命周期内持续的高级事务(“视图中的打开会话”)。ORM 框架通常提供管理这些事务的工具。

  • 域层中的事务。它们可以由域对象或服务启动。例如: FundsTransferService.Transfer() 可以在内部使用交易。在这里,您可以使用您平台的基本事务处理。

实体(或聚合根)是否应该持有对域服务的引用?如果是这样,他们应该如何获得参考资料?

是的,实体有时可以调用域服务。域服务包含不属于任何实体的域规则和行为。您可以硬编码这些依赖项或将它们注入实体中,具体取决于您想要的解耦级别。

为了获得实体的新 id,我们必须调用一个存储过程,该过程已包装在存储库中。你会在哪里引用这个?需要创建许多子实体的实体上的一些复杂方法需要引用这个?

我不建议为按需访问的实体生成 ID。正如 David 指出的那样,当您新建实体时,在语言级别生成 Guid 通常是一个更好的主意。

如果您仍想走 ID 生成路线,调用 ID 生成器通常是实体工厂的工作,而不是实体本身。

于 2012-06-28T12:59:54.663 回答