DDD 工厂责任

DDD Factory Responsibility

如果有以下代码。

public class CountryFactory : IEntityFactory
{
    private readonly IRepository<Country> countryRepository;

    public CountryFactory(IRepository<Country> countryRepository)
    {
        this.countryRepository = countryRepository;
    }

    public Country CreateCountry(string name)
    {
        if (countryRepository.FindAll().Any(c => c.Name == name))
        {
            throw new ArgumentException("There is already a country with that name!");
        }

        return new Country(name);
    }
}

从 DDD 方法来看,创建 Country 的正确方法是正确的。或者最好有一个 CountryService 检查一个国家是否存在,如果不存在,只需调用工厂 return 一个新实体。这意味着服务将负责持久化实体而不是工厂。

我对责任应该归于何处有些困惑。特别是如果需要创建更复杂的实体,这不像创建国家那么简单。

在 DDD 中,工厂用于封装复杂对象和聚合创建。通常,工厂不是作为单独的 classes 实现的,而是作为新聚合 return 的聚合根 class 上的静态方法实现的。

工厂方法比构造函数更适合,因为您可能需要技术构造函数来实现序列化目的,并且 var x = new Country(name) 在您的通用语言中意义不大。这是什么意思?为什么创建国家需要名字?你真的创建国家,新国家出现的频率,你甚至需要模拟这个过程吗?如果您开始考虑除了战术模式之外的模型和无处不在的语言,所有这些问题都会出现。

工厂必须 return 有效对象(即聚合),检查其内部的所有不变量,但不检查外部。工厂可能会接收服务和存储库作为参数,但这也不是很常见。通常,您有一个应用程序服务或命令处理程序执行一些验证,然后使用工厂方法创建一个新聚合并将其添加到存储库。

Lev Gorodinski 在这里也有一个很好的回答 Factory Pattern where should this live in DDD?

此外,Red Book 的第 11 章详细描述了工厂的实现。

将存储库注入工厂是可以的,但这不应该是您的首要考虑。出发点应该是:您的业务领域需要什么样的一致性?

通过在域层的 CountryFactory 中检查国家/地区名称的唯一性,您会给自己这样的印象,即国家/地区将始终保持一致。但唯一的聚合是 Country 并且由于没有 AllCountries 聚合作为一致性边界,因此无法保证遵守此不变量。在您检查之后,总是有人可以潜入一个与添加的国家名称完全相同的新国家/地区。您可以做的是将 CreateCountry 操作包装到一个事务中,该事务将锁定整个国家集(如果您使用 RDBMS,则锁定整个 table),但这会损害并发性。

还有其他选项需要考虑。

  • 为什么不利用数据库唯一约束来强制国家名称不变?作为补充,您还可以在 UI 级别设置另一个检查点,以警告用户他们输入的国家/地区名称已被使用。这将需要另一个“查询”服务,该服务仅调用 CountryRepository.GetByName() 但返回的国家/地区预计不会被修改。

    很快您就会意识到实际上有两种模型 - 一种可以在给定时刻及时为您提供一些域数据,以便您可以在用户界面上显示它,另一种可以公开操作(AddCountry) 并将保证域不变量始终成立。这是迈向 CQRS 的第一步。

  • 添加或修改国家/地区的频率是多少?如果它那么高,我们真的需要一个国家名称在任何时候都是唯一的吗?如果我们放宽限制并允许用户临时创建重复的 Country name ,它不是可以解决很多问题吗?一种机制可以稍后检测到重复项并采取补偿措施,搁置新添加的国家/地区并联系用户要求他们更改名称。 A.k.a最终一致性而不是立即一致性。

  • Country 是否需要聚合?如果它是一个值对象并在使用它的每个实体中复制,成本是多少?