【问题标题】:What does exactly domain service mean in DDD and how to implement it?域服务在 DDD 中究竟意味着什么以及如何实现它?
【发布时间】:2018-03-02 11:41:10
【问题描述】:

我正在开发一个实现 DDD 概念的 Web 应用程序,我知道 DDD 中的域服务是:

域服务:封装不自然的业务逻辑 适合域对象,不是典型的 CRUD 操作 - 这些将属于存储库。

但我对域服务是域模型中的类、接口还是方法以及我的聚合根/域实体将如何调用该服务感到困惑?

是否还需要返回聚合根或域实体作为域服务?

如果我的聚合根需要检查电子邮件是否存在,或检查密码哈希匹配(需要调用服务/存储库来访问数据库)。我需要在域服务或应用程序服务中实现该代码吗?

【问题讨论】:

    标签: c# service entity domain-driven-design aggregateroot


    【解决方案1】:

    但我对域服务是域模型中的类、接口还是方法感到困惑

    ClassInterface,取决于抽象级别。在某些情况下,它需要一些第三方 stateless(!) 库,并且您将其作为接口与基础设施层中的实现(在此层中您可以调用第三方库)。

    我的聚合根/域实体将如何调用该服务

    如果它是一个接口,那么您可以将服务传递给聚合的方法或在聚合之外调用它,并将输出作为聚合方法的参数传递(首选方式,因为它可以最大限度地减少聚合的依赖性)。

    如果它是一个类,您也可以在聚合中实例化+使用它,但这会创建从聚合到域服务的依赖关系;这在任何情况下都没有错,但你应该在使用它之前考虑一下。

    是否还需要返回聚合根或域实体作为域服务?

    不,它可以返回驻留在域层中的任何内容(原始值或值对象)。

    如果我的聚合根需要检查电子邮件是否存在,或检查密码哈希匹配(需要调用服务/存储库来访问数据库)。我需要在域服务或应用程序服务中实现该代码吗?

    如果它需要进行 IO 调用,那么它不是域服务,而是基础架构服务或应用程序服务。

    如果我的聚合根需要检查电子邮件是否存在

    在应用服务从持久性中加载该信息后,该信息必须作为原始值或值对象传递给聚合。

    【讨论】:

    • 谢谢你的回答,但是你所说的检查电子邮件存在的状态是什么意思。我的意思是服务会访问数据库来检查这封电子邮件是否已经存在(没有状态)。我看到了一些人们说我们可以在域和应用程序服务中做到这一点。
    • @SimpleCode 你不应该在领域层(聚合、值对象或服务)内进行任何 IO;这包括阅读。
    • @SimpleCode 对于应用程序层,您可以(并且很可能需要)进行 IO 调用,但不允许在应用程序服务内部维护状态(实际上是在任何 service 中)
    • @SimpleCode 我弄错了,我想写“IO调用”而不是“状态”;查看我编辑的答案
    • 但我认为我们将 DDD 中的存储库视为域服务,如果我的域服务访问数据库以检查此电子邮件是否已经存在,那么根据您的回答,不允许这样做会违反 DDD。如果我弄错了请纠正我enterprisecraftsmanship.com/2016/09/22/…
    【解决方案2】:

    但我对域服务是域模型中的类、接口还是方法以及我的聚合根/域实体将如何调用该服务感到困惑?

    域服务可以是实现某些接口的对象,例如具有具体实现对象的 IShippingCostCalculator,例如 Company1ShippingCostCalculator 和 Company2ShippingCostCalculator。域服务可以从域模型(聚合、实体)或应用服务中调用。

    如果我的聚合根需要检查电子邮件是否存在,或检查密码哈希匹配(需要调用服务/存储库来访问数据库)。我需要在域服务或应用程序服务中实现该代码吗?

    对于需要一些 HTTP 调用或任何其他的外部服务,您必须在域中具有接口并在某些基础架构端实现,并使用 DI 来注入具体实现。例如域层中的 IEmailSender 接口和域层外的 EmailSender 实现。 应用服务仅用于域模型协调(编排)。

    【讨论】:

      【解决方案3】:

      谈论 DDD 问题很困难,因为每个人都需要有共同的上下文知识,这样解释和业务规则才有意义。因此,我使用支付和购物车等众所周知的概念。

      在以 DDD 为中心的实现中,购物车是一个聚合根,每个购物项目都是一个域实体,每个项目的价格都可以建模为一个价值对象。

      这些对象中的每一个都抽象出一些业务逻辑,因此它们不是贫乏的领域模型。例如

      Price 是一个值对象,它可能包含诸如转换为不同货币、用于加法或减法的特殊逻辑(考虑四舍五入)等方法。

      与 Price 类似,cart-item 也有一些特殊的方法,例如添加特定商品的数量或其颜色,但由于我们可以在我们的系统(购物车)中唯一标识它,因此它是一个实体。

      最后,购物车是一个聚合根。它包含购物项目,并且可以应用可能涉及购物车项目的业务规则,例如,如果购物车中所有时间的总价格达到阈值,则将免费奖励项目添加到购物车。

      但这些抽象是不够的,有时我们可能有复杂的业务规则,其中涉及多个聚合根、实体……所以在这种情况下,我们定义域服务。

      下一个抽象的区别非常好。假设我们的系统想要处理一个涉及与外部世界交互的请求(发送通知,...),所以我们将外部世界抽象为接口,并在需要时提供实现(端口和适配器)。然后我们使用这些接口以及我们的聚合根......来实现我们所需的功能。

      如果在我们的实现过程中,我们正在建模和实现业务逻辑(检查不变量,...),那么我们正在实现域服务,但如果不涉及业务逻辑并且我们只是在协调一些 Adpaters(端口)和域实体,聚合......那么它就是一个应用服务。

      基于这些假设,我有以下建议:

      但我对域服务是域模型中的类、接口还是方法以及我的聚合根/域实体将如何调用该服务感到困惑?

      (业务/应用程序)服务协调/管理根/域实体,反之亦然。

      如果我的聚合根需要检查电子邮件是否存在,或检查密码哈希匹配(需要调用服务/存储库来访问数据库)。我需要在域服务或应用程序服务中实现该代码吗?

      如果它是一种重复出现的模式,也是您的业务逻辑的一部分,那么它应该被视为域服务。如果它不涉及业务逻辑,则它是一个应用程序服务。

      【讨论】:

        【解决方案4】:

        简而言之:服务总是作为接口公开,不是为了可交换性、可测试性等,而是以契约的形式公开一组内聚的操作。除了这个含义之外,通常还有无国籍的假设和根据 GRASP 的纯粹制造的想法。阅读更多here

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-03-23
          • 1970-01-01
          • 2013-06-21
          • 2020-06-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多