【问题标题】:Domain service injecting into domain entities域服务注入域实体
【发布时间】:2014-01-27 10:47:04
【问题描述】:

我正在学习领域驱动设计,但我对实体和向其中注入领域服务感到有些困惑。我发现了这个blog,结论是向实体注入服务是个坏主意。我部分同意这一点,但在这种情况下该怎么做: 我有用户实体,它是一个聚合根,其中有密码值对象。它看起来像这样:

密码值对象:

public class Password  
{  
    public string Hash { get; private set; }

    public string Salt { get; private set; }

    private readonly IHashGeneratorService _hashGeneratorService;

    public Password(IHashGeneratorService hashGeneratorService)
    {
        _hashGeneratorService = hashGeneratorService;
    }

    public void GenerateHash(string inputString)
    {
        //Some logic

        Salt = hashGeneratorService.GenerateSalt();
        Hash = hashGeneratorService.GenerateHash(inputString);
    }
}

用户实体:

public class User
{
    public Password Password { get; private set; }

    public User(IHashGeneratorService hashGeneratorService)
    {
        this.Password = new Password(hashGeneratorService);
    }
}

在这种情况下,如果我通过工厂创建用户实体,我需要向工厂构造函数或 Create() 方法提供 IHashGeneratorService 实现。之后,如果我的工厂被使用,例如。 SomeUserService 我必须为其提供实现(例如,通过 ctor 注入)。等等... 老实说,我闻起来很香,因为我的很多类都依赖于哈希生成器服务实现,但只有 Password 类使用它。而且它也违反了我假设的 Password 类的 SRP 原则。

我找到了一些解决方案:

  1. 使用服务定位器。但它也有异味,因为它是一种反模式,如果我们使用它,就很难测试和管理实体。

  2. 直接在密码方法中实现散列算法。

  3. 坚持我所拥有的 :) 上面提到了缺点,优点是我的类更容易测试,因为我可以提供模拟服务而不是完整实现。

我个人倾向于将我的代码重构为第二种解决方案,因为它不会破坏 SRP(或者它会破坏?:)),类不依赖于哈希服务实现。还有什么? 或者您有其他解决方案吗?

【问题讨论】:

  • +1 用于基础设施层和应用程序层的使用以及值对象(这是我的错误,我在我的帖子中纠正了它:))但我不确定应用程序层是否应该负责用于创建用户实体。我认为最好将其留在工厂中,因为工厂创建的用户实体已初始化所有需要的字段,包括密码值对象。
  • 我毕竟发布了一个答案 =P 好吧,如果实例化 User 对象非常复杂,可以在工厂中完成(作为域服务实现?)。可以将相关服务注入工厂,以便在进入 User 聚合之前实例化 Password 值对象,就像在应用程序服务中所做的那样。
  • 小心,你的Password实际上不是一个值对象,因为GenerateHash改变了它的状态。
  • @Alexander Langer - 我认为值对象没有自己的身份,不能单独存在,但它们的状态可以改变
  • @MadMax 值对象应该是不可变的。如果值更改,则使用更改的值创建一个新的值对象,并用新值替换原始值对象。检查martinfowler.com/bliki/ValueObject.html 以供参考。

标签: c# dependency-injection domain-driven-design


【解决方案1】:

我对 DDD 很陌生,但是我相信散列密码不是域的问题,而是技术问题,就像持久性一样。哈希服务应该在 domain 中定义它的接口,但它的实现是在 infrastructure 层中。 应用程序服务然后会使用 hash service 对密码进行哈希处理并在之前创建一个Password 实例(应该是一个值对象)将其传递给 User 聚合根。

在某些情况下,聚合必须使用服务,例如依赖解决方案非常复杂且特定于域时。在这种情况下,应用程序服务可以将域服务传递给聚合方法。然后聚合将双重分派到服务以解析引用。

有关更多信息,您可以阅读由 Vaughn Vernon 撰写的实施领域驱动设计一书。他在第 362 页(模型导航)中谈到了这一点,而且在本书的其他几个地方也谈到了这一点。

【讨论】:

    【解决方案2】:

    不知道为什么,为什么只考虑注入构造函数参数。 AFAIK,DI 容器注入属性或字段是一个常见功能。例如,使用 MEF,您可以编写如下内容:

    class SomeUserService : ISomeUserService
    {
        [Import]
        private IHashGeneratorService hashGeneratorService { get; set; }
    
        // ...
    }
    

    并仅在您真正需要的那些类型中注入依赖项。

    【讨论】:

    • 没错,但我想知道将服务注入(而不是如何做)到实体中是否是个好主意。此外,如果我使用属性注入,那么使用 ctor 注入进行测试会变得更加困难。
    • @MaciekKorzeniewski:就个人而言,我不喜欢这个想法。我更喜欢方法,其中实体只是数据容器,逻辑在服务层之外实现。原因是我根本不想测试我的域实体。
    • 好吧,但是您的解决方案不是贫血的域模型吗?
    • 就像我上面写的,我的散列服务只在密码实体中使用,所以我只需要将它注入到密码类和属性注入,因为其他类不依赖于拥有服务实现,但这并没有让我对实体注入服务感到不那么困惑。
    • 使用属性注入使类与特定的 DI 容器实现紧密耦合,并使您的 API 依赖于它的合同(它向用户隐藏了需要提供的额外依赖项)。我认为这种 DI 风格是应用程序框架需要使用无参数 ctor 的最后手段。就像在 MVC 过滤器中一样。
    猜你喜欢
    • 1970-01-01
    • 2021-06-20
    • 2018-03-14
    • 1970-01-01
    • 1970-01-01
    • 2017-03-13
    • 2013-06-04
    • 2015-12-30
    • 2021-07-02
    相关资源
    最近更新 更多