【问题标题】:Why must Domain services use domain objects as parameters and return values?为什么域服务必须使用域对象作为参数和返回值?
【发布时间】:2013-01-14 20:37:10
【问题描述】:

当操作在概念上不属于任何实体或值时 对象,那么我们不是将行为强加到对象中,而是 应该创建一个域服务。

Service 的接口应该根据其他定义 域模型的元素。换句话说,参数和返回 Service 的值应该是域对象

a) 为什么域服务应该/必须使用域对象作为参数和返回值?

b) 为什么 DDD 不要求实体和值对象的方法使用域对象作为参数和返回值?为什么这个约束只放在服务上?

谢谢

EULERFX:

1)

这两个约束都促进了不变性和函数式风格

a) 这两个约束如何促进不变性?

b) 什么是函数式风格?

c) 所以我们应该尝试(因为它可能并不总是可以使用 force )强制服务使用域对象作为参数和返回值,即使该服务(即行为)可能更自然接受/返回非域对象?

2)

实体和值对象组成更多的原始类型来形成 复杂的类型和某些行为可能取决于原始参数。

那么是不是由于域实体/值对象的某种内在特征,在大多数情况下它们的行为(即它们的操作)对原始类型(即使用原始类型作为参数)进行操作?如果是,那么在大多数情况下,这种内在特征存在于域对象中,但很少出现在域服务中?

第二次更新:

这两个约束如何促进不变性?

这个想法是域服务不会改变状态和所有状态 更改是通过参数显式进行的。

a) 不改变自己的状态或某些域对象的状态?由于域服务应该是无状态的,我假设你的意思是它不应该改变 DO 的状态?换句话说,服务通过确保它打算修改的任何 DO 都作为参数传递给它(即传递给它的操作)来促进不变性

b) 但是如果要被服务修改的 DO 没有作为参数传递,那么我们说域服务改变了这个 DO 的状态?

c) 改变 DO 的状态被认为是一件坏事的原因是因为它不能提高清晰度(即,在查看服务操作的签名时并不立即显而易见,哪些 DO 将获得它们的状态被操作改变了)?

d) 如果域服务要修改作为参数传递给它的 DO 的状态,如果将用于更改此 DO 状态的值也作为参数传递给服务。如果是,是因为它促进了清晰度还是……?

2) 我仍然不明白返回值与参数的类型相同如何促进不变性?

EULERFX 3

一)

域服务可以通过返回新实例来避免状态突变 对象而不是修改传入的对象。

不是一个问题,更多的是一种观察,但在理解为什么这种服务行为在大多数领域模型中很常见,甚至这种行为是在对领域建模时自然产生还是我们必须将其强制纳入概念 a有点?!

b)

是的,尽管在这种情况下,域对象最好 自己变异。

DO 应该改变自身的主要原因是因为在特定 DO 上执行突变的代码集中在一个地方,所以如果我们需要检查这段代码,我们知道在哪里寻找它?

【问题讨论】:

    标签: domain-driven-design


    【解决方案1】:

    a) 这不是一个严格的限制,但有一定的优势。该规则背后的想法是域服务包含补充现有实体和值对象的功能。另一个非严格约束是域服务方法的参数和返回值都属于同一类型的操作的闭包。这两个约束都促进了不变性和函数式风格,从而减少了副作用并更容易推理代码、重构代码等。

    可能有一个域服务方法接受既不是实体也不是值对象的原始类型。但是,大量使用原始类型可能会导致primitive obsession

    b) 此约束可以在一定程度上应用于实体和值对象级别。实体和值对象组成更多的原始类型以形成复杂类型,并且某些行为可能取决于原始参数。这个原始参数本身可以变成一个值对象。

    更新

    刚从 DDD 聚会回来,我有机会与Implementing Domain-Driven Design 的作者Vaughn Vernon 讨论这个问题。他同意指定的约束条件并不严格。换句话说,在某些情况下,域服务方法完全可以通过原始类型参数化。

    这两个约束如何促进不变性?

    这个想法是域服务不会改变状态,并且所有状态更改都通过参数显式地进行。这是pure function 的精髓。鉴于域服务补充了实体,它们的方法应该用这些术语来表达。

    什么是功能风格?

    我指的是functional programming。函数式编程通常需要不变性和纯函数。函数式方法的另一个特点是 declarative style 与命令式对比。

    所以我们应该尝试(因为可能并不总是可以使用武力) 强制服务使用域对象作为参数并返回 价值观

    没有。如果原始类型足以满足操作,则没有理由将其强制转换为其他类型。实体和值对象的使用只是一个指导方针,有些人更喜欢比其他人更严格。例如,有些使用显式类型来表示每个实体的身份。因此,您可以创建一个名为 OrderId 的值对象来表示订单的身份,而不是使用 int

    是不是由于域的某种内在特征 在大多数情况下它们的行为(即它们的 操作)对原始类型进行操作(即使用原始类型作为 参数)?

    我不会说它是 DDD 固有的。我指的是更一般的组合概念——复杂实体(非 DDD)由更简单的实体组成。从这个意义上说,对复杂实体的操作将根据组成部分来表示是有道理的。

    更新 2

    a) 域服务可以通过返回对象的新实例而不是修改传入的对象来避免状态突变。这样,方法的签名完全描述了它的作用,因为没有副作用。

    b) 域服务可以改变传递给它的对象的状态,在这种情况下,返回类型可能是完整的。然而,这是不太理想的 - DO 改变自己的状态会更好。

    c) 是的,这是其中的一部分。不变性和纯度使您可以重构代码,就像使用代数方程分解代数一样。另一个原因是它使对代码的推理更容易,因为如果您查看一段不可变数据,您可以确定它在其剩余范围内不会改变。

    d) 是的,尽管在这种情况下,域对象自己变异会更好。此突变将由周围的应用程序服务调用。很多时候,我将域服务传递给实体行为方法,为它们提供他们无法直接访问的功能。

    e) 操作闭包的概念本身并不能促进不变性,但它是不可变代码的一个特征。原因是如果一个域服务方法接受一个类型为 T 的值并返回一个类型为 T 的值,它可以表明它返回一个由封装操作产生的新值 T。这是不变性的一个特点,因为操作产生的变化被显式地作为一个新对象。

    更新 3

    a) 这与传统 OOP 的关系比与 DDD 的关系更大。 OOP 试图将移动的部分隐藏在对象后面——封装。 FP 试图最小化移动部分 - 不变性。在某些情况下,不可变性可以被视为更“自然”。例如,在以事件为中心的场景中,事件是不可变的,因为它们是已发生事件的记录。您不会改变已经发生的事情,但您可以创建补偿操作。

    b) 同样,这与 OOP 的关系比 DDD 更多,并且基于 information expert pattern,它基本上指出数据上的行为应尽可能接近该数据。在 DDD 中,这意味着实体应该尽可能地封装所包含的数据,以确保自身的完整性。

    【讨论】:

    • 我做了最后一次更新。如果可以,请看一下,否则感谢您的精彩解释
    猜你喜欢
    • 1970-01-01
    • 2018-07-23
    • 1970-01-01
    • 1970-01-01
    • 2023-01-24
    • 2022-06-15
    • 2018-10-07
    • 2016-01-31
    • 2012-10-30
    相关资源
    最近更新 更多