【问题标题】:Domain Driven Design - testability and the "new" keyword领域驱动设计 - 可测试性和“新”关键字
【发布时间】:2023-03-19 08:50:01
【问题描述】:

我一直在尝试在我的新项目中遵循领域驱动的设计方法。我一直通常使用 Spring 进行依赖注入,它很好地将我的应用程序代码与构造代码分开,但是,使用 DDD,我似乎总是有一个域对象想要创建另一个域对象,它们都有状态和行为。

例如,给定一个媒体文件,我们希望将其编码为不同的格式 - 媒体资产调用转码服务并接收回调:

class MediaAsset implements TranscodingResultListener {

    private NetworkLocation permanentStorage;
    private Transcoder transcoder;

    public void transcodeTo(Format format){
        transcoder.transcode(this,format);
    }

    public void onSuccessfulTranscode(TranscodeResult result){
        Rendition rendition = new Rendition(this, result.getPath(), result.getFormat());
        rendition.moveTo(permanentStorage);
    }

}

这会引发两个问题:

  • 如果再现需要一些依赖项(例如 MediaAsset 需要“转码器”)并且我想使用 Spring 之类的东西来注入它们,那么我必须使用 AOP 才能运行我的程序,但我不会喜欢。
  • 如果我想对 MediaAsset 进行单元测试,以测试新格式是否已移至临时存储,那么我该怎么做?我无法模拟再现类来验证它是否调用了它的方法...将创建真正的再现类。

我考虑过用工厂来创建这个类,但是仅仅包含导致问题的“new”关键字就需要很多代码开销。

这里是否有我遗漏的方法,或者我只是做错了?

【问题讨论】:

  • 注入依赖不需要使用AOP。 @Autowired 之类的东西需要 AOP。但是要实现注入,一个构造函数参数或一个 setter 方法就足够了。关键是摆脱对象内部包含逻辑的任何新语句,而是在外部创建所需的依赖项。即使我使用 Autowire 魔术,我也经常定义至少一个包可见构造函数,它接受所有必需的依赖项作为参数,并且可以用来单独测试一个 bean。
  • “关键是摆脱对象内部包含逻辑的任何新语句,而是在外部创建所需的依赖项。” - 这就是问题 - 怎么做?工厂?
  • 正如已经建议的答案,如果您的对象需要创建新对象,那么工厂就是要走的路。因此,您可以将 new 转换为对工厂的静态依赖,并且可以使用构造函数、setter 或魔法注入。

标签: java spring unit-testing domain-driven-design


【解决方案1】:

我认为在这种情况下注入 RenditionFactory 是正确的方法。我知道这需要额外的工作,但你也从你的班级中删除了违反 SRP 的内容。在业务逻辑中构造对象通常很诱人,但我的经验是,注入对象或对象工厂会在 100 次中获得 99 次的回报。特别是如果提到的对象很复杂,和/或如果它与系统资源交互。

【讨论】:

    【解决方案2】:

    我假设您的单元测试方法是单独测试MediaAsset。这样做,我认为工厂是常见的解决方案。

    另一种方法是测试整个系统(或几乎整个系统)。让您的测试访问外部接口[1](用户界面、Web 服务接口等),并为系统访问的所有外部系统(数据库、文件系统、外部服务等)创建测试替身。然后让测试注入这些外部依赖。

    这样做,您可以让测试完全与行为有关。测试与实现细节分离。例如,您可以对Rendition 使用依赖注入,或者不使用:测试不关心。此外,您可能会发现MediaAssetRendition 不是正确的概念[2],您可能需要将MediaAsset 拆分为两部分并将其中的一半与Rendition 合并。同样,您无需担心测试就可以做到这一点。

    (免责声明:在外层进行测试并不总是有效。有时您需要测试常见的概念,这需要您编写微测试。然后您可能会再次遇到此问题。)

    [1] 最好的级别实际上可能是“域界面”,在用户界面下方的一个级别,您可以在其中使用域语言而不是字符串和整数,并且可以在其中谈论域操作而不是按钮单击和焦点事件。

    [2] 也许这实际上是您的问题:MediaAssetRendition 是正确的概念吗?如果你问你的领域专家,他知道这些是什么吗?如果不是,你真的在​​做 DDD 吗?

    【讨论】:

    • 感谢您的回答 - 我确实想单独进行测试,但也进行集成和验收级别测试。我不认为做一个就意味着你不必做另一个?
    • 由于它们的作用域不同,它们相互补充。显然,单元测试无法发现对象/模块之间的错误通信,但同时很难创建也涵盖单个对象功能的集成测试。单元测试简化了重构和发现单个主题中的错误。如果没有单元测试,失败的集成测试可能会导致大量的调试会话来追踪源代码。
    • @Paul 在您的领域中,MediaAssetRendition 是众所周知的概念吗?
    • 是的,它们用于通用语言并且是核心概念。
    • 关于我们是否在做 DDD 的问题是一个很好的问题。我不确定我们是否真的如此——我们的领域并不是特别复杂。我们可能只是在避免贫血的领域模型反模式。
    猜你喜欢
    • 2016-09-29
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    • 2011-06-21
    • 2016-07-18
    • 2013-09-16
    相关资源
    最近更新 更多