【问题标题】:DDD - Domain Service Implementation: Domain or InfrastructureDDD - 领域服务实施:领域或基础设施
【发布时间】:2021-01-08 14:37:02
【问题描述】:

我有这个经典的 DDD 问题;我有一个域服务“DetectPriority”可以做一些事情。

PM 让我创建 2 个不同的服务;一个 INTERNAL(包含完整的业务规则并涉及许多其他领域模型)和另一个 ETERNAL(一个简单的 API 调用)。

域中有一个接口“DetectPriorityInterface”。 两个实现必须同时处于活动状态;一种“混合”必须实时选择一个而不是另一个。

问题是:这些实现(两个实现)应该放在哪里:域层还是基础设施层??

内部实现充满了业务规则,应该驻留在领域层。 外部实现是一个简单的调用,应该存在于基础架构中。

我们应该把两者都放在基础设施层吗?

谢谢

编辑

实际上我们有一个接口“DetectPriority”和三个实现,都在我们的领域层(临时“错误”解决方案):

  1. InternalDetector(带有业务规则)
  2. ExternalDetector(使用外部 API 调用)
  3. MixerDetector(获取两个实现并处理选择)

客户端使用接口,所以对于应用层,所有这些东西都是透明的;接下来,我们将移除 Internal(或 External)和 Mixer 并仅使用一个实现。 (所有这些背后的想法是了解谁表现更好,这是一个 A/BN 测试)

我们的内部争论是:因为 InternalDetector 有一些仅属于该检测器的域规则,它应该存在于基础设施层,因为它不是通用域规则。我们中的一些人不同意这一点,因为在 InternalDetector 中我们只有业务规则,而在 Infra Layer 中看不到。

可能正确的方法应该是在Domain中添加Internal,在Infra中添加External ..但似乎有点混乱..

将所有内容放在 Infra 层中对开发人员来说更具可读性...

我们没有在书中找到一些东西,因为通常你只有一个域服务的实现......

【问题讨论】:

  • 只是为了确保我正确理解了这个问题,在您的应用程序层中,您决定应该使用外部检测器还是内部检测器,对吧?如果是,您根据什么标准做出此决定?
  • 这里最有趣的方面是您如何在两个检测器之间做出决定,以及该决定是否被视为业务逻辑或基础架构代码?代码应该将检测器视为由谓词选择的任意策略,还是内部和外部检测器之间的业务存在根本区别?例如。他们只是Detectors 还是有BasicDetectorAdvancedDetector 接口概念,例如,高级接口概念是由外部提供的。
  • @GFCoder977 如果是关于 A/B 测试,那么选择检测器是应用程序级别的决定。我已根据问题中的新信息更正了我的答案。
  • @GFCoder977 你说,“因为 InternalDetector 有一些域规则只属于那个 Detector,它应该存在于基础设施层,因为它不是通用域规则” .但是如果在 A/B 测试之后,你选择离开InternalDetector 这些规则将成为你的领域逻辑。

标签: domain-driven-design


【解决方案1】:

简短的回答是,您应该在域层实现一个内部服务,在基础设施层实现一个外部服务,正如您在问题中所说的那样。这样一来,一切都将就位。

另外需要考虑的重要一点是,决定调用哪个服务的代码也应该在域层中。正如我从您的问题中可以想象的那样,您可以使用某些业务规则来决定使用哪个检测器。一个检测器在您的应用程序中实现,而另一个检测器未实现这一事实只是您系统的实现细节。实际上,您只是根据某些条件决定使用一组业务规则或另一组业务规则。 这是一个商业决策

DDD 通常是关于艰难的妥协。但是当您正在寻找一个好的折衷方案时,我建议永远不要将域逻辑移到域层之外,并且永远不要将域层的引用添加到其他层。

这是必不可少的。

下面是一个示例,说明如何在不违反这些规则的情况下解决此任务:

// Names in this code should be changed to something with business 
// meaning. For example `externalDetector` can be `governmentDetector` 
// and `internalDetector` can be `corporateDetector`.

// Declare a service interface in the domain layer
public interface DetectPriority {}


// Inject both detectors in the domain service.
// Your dependency injection code should inject here 
// an internal implementation and an external one, 
// implemented in the infrastructure layer.
// So your DI code knows about different implementations
// but the domain service doesn't.
// For the domain service it's just two implementations 
// of domain interface IDetector
IDetector _externalDetector;
IDetector _internalDetector;


// Implement the method of the domain service like this:
public Priority Detect() 
{
    if (weShouldUseExternalSetOfRules) 
    {
        return _externalDetector.Detect(); // this one is implemented in your infrastructure layer
    }
    else 
    {
        return _internalDetector.Detect(); // this one is implemented in your domain
    }
}

在这个解决方案中你可以看到:

  1. 所有域逻辑(内部检测器的实现和决定使用哪组规则)都放置在域层中。
  2. 我们的域中没有对基础设施层的引用。域服务只引用了IDetector接口,但是这个接口是在域层声明的。
  3. 领域层中没有基础设施代码。在这种情况下,基础设施代码的含义类似于“使用查询字符串中的这组参数调用该 REST 服务的 GET 方法”。显然,这段代码将在externalDetector 实现中。

为了确保这是一个好方法,您可以查看this repository 以及著名的 Eric Evans 书中的示例 DDD 应用程序。您可以找到在域层中声明的service interface 和在基础设施层中实现的service itself。不幸的是,在这个应用程序的领域层内没有使用这个服务接口的例子。但它是在域层内部声明的,以使这种用法成为可能。

您可以在this great article 中找到相同的方法和很好的解释。


编辑

根据问题中的新信息,如果是关于 A/B 测试,那么选择检测器是应用程序级别的决定。所有其他事情保持不变。所以:

  • MixerDetector应该在应用层
  • DetectPriority 接口 - 在域层中
  • 领域层中的InternalDetector
  • ExternalDetector 在基础设施层

然后,您的探测器就不需要“企业”名称,因为它们实际上是 InternalDetectorExternalDetector

【讨论】:

    【解决方案2】:

    我们应该把两者都放在基础设施层吗?

    通常不会,不会。除其他外,这将使您的依赖关系图变得一团糟。我们不希望域代码依赖于基础设施代码(拥有域模型的动机之一是,您可以在不与运行域模型的上下文紧密耦合的情况下实现域的逻辑——引入基础设施依赖与该目标背道而驰)。

    这并不一定意味着基础架构代码“很远” - 请参阅package by feature vs package by layer。它们是不同的职责(在单一职责原则意义上),因此两者之间通常会有一些分离。

    两者有很大不同的一个方面:故障模式——通过网络通信的健壮代码需要尊重网络不可靠的事实,但这不是关注的问题,所以我们通常不想用网络应急逻辑污染我们的域代码。

    但是,如果您愿意,请忽略所有这些 - 真正的启发式方法很简单:我们希望代码安排在整个生命周期内最容易维护。如果在您的上下文中这意味着将域代码放入基础架构层,那么您应该这样做。

    DDD 和其他风格的指导原则主要是为了帮助您避免通过决定“立即”做容易的事情来避免增加终身维护成本的陷阱。

    【讨论】:

    • 您是否曾经成功地使用过逐层封装的方法,所有基础设施关注点都与域关注点相邻?您是在这些功能包中进一步逐层打包还是使用类六边形架构(依赖倒置)?
    【解决方案3】:

    我用来在域层中保留域服务实现,它们没有基础设施依赖。需要基础设施依赖的领域服务接口的实现应该驻留在基础设施层中。

    在您的情况下,您还需要考虑的是,在运行时实例化 DetectPriorityInterface 的具体实现的代码必须驻留在基础设施层中,并且它还具有直接依赖关系到外部域服务

    我建议你有一些工厂来决定根据某种参数创建一个或另一个域服务。但是您仍然可以使用 工厂接口,您可以将其放入域层。我们称它为 PriorityDetectorFactoryInterface 或类似名称。只有那个工厂的具体实现,我们称之为 PriorityDetectorFactory 将驻留在基础设施层。

    如果您有一些应用程序服务负责处理优先级检测发挥作用的用例,您可以将 PriorityDetectorFactoryInterface 传递到此应用程序服务中。在运行时,工厂接口(即 PriorityDetectorFactory)的具体实现将被注入到应用程序服务中。这样,您还可以使您通常只定义用于编排用例的工作流的应用程序层不依赖于基础架构。

    这样你就有了:

    • DetectPriorityInterface 在您的域层
    • InternalPriorityDetector(实现 DetectPriorityInterface)在您的 域层
    • ExternalPriorityDetector(实现 DetectPriorityInterface)在您的基础设施层
    • PriorityDetectorFactoryInterface 在您的域层中也是如此
    • PriorityDetectorFactory(实现 PriorityDetectorFactoryInterface)在您的基础设施层
    • ...以及提到的应用服务在你的应用层处理你的用例

    注意:这一切都基于您的内部域服务实现真正没有依赖于域层本身的东西的假设。

    【讨论】:

      猜你喜欢
      • 2018-07-03
      • 1970-01-01
      • 1970-01-01
      • 2014-06-16
      • 1970-01-01
      • 1970-01-01
      • 2010-11-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多