在 DDD 中如何通过 DTO 传递值对象?

In DDD how to pass Value Objects via DTO?

在我的域中,每个域实体可能有许多值对象。我创建了值对象来表示金钱、重量、计数、长度、体积、百分比等。

这些值对象中的每一个都包含一个数值和一个度量单位。例如。 money 包含货币价值和货币 ($, euro,...) , weight 包含数值和重量单位 (kilo, pound, ...)

在用户界面中,这些也并排显示:字段名称、其值后跟其伴随的单位,通常在属性面板中。域实体具有暴露给 UI.

的等效 DTO

我一直在寻找将 DTO 中的值对象传输到 UI 的最佳方法。

  1. 我是否只是将特定值对象公开为 DTO 的一部分?
  2. 我是否在 DTO 中公开提供 name/value/unit 的通用 "value object" 等价物?
  3. 我是否将其拆分为 DTO 中单独的 name/value/unit 成员,只是为了在 UI 中重新组合它们?
  4. 我是否将它们作为 KeyValuePair 或 Tuple 传输到 DTO 中?
  5. 还有别的吗?

我进行了深入搜索,但似乎没有其他问题可以完全解决这个问题。非常感谢任何建议!

编辑: 在 UI 中,值和单位都可以更改并发送回域进行更新。

如果这些是单向传输,我会倾向于同意上面 debuggr 的评论;值对象不是 真正的 域对象 - 它们没有可以改变其状态的行为,因此在许多方面它们只是专门化的 "bit-buckets" 因为您可以序列化它们而不会丢失上下文。

但是;如果您遵循 DDD 实践(或者如果您的后端正在使用多线程等),那么您的值对象是不可变的,即它们可能看起来像这样:

public class Money
{
    readonly decimal _amount;
    readonly string _currency;
    public decimal Amount {get{return _amount;}}
    public decimal Currency {get{return _currency;}}

    public Money(decimal amount, string currency)
    {
        //validity checks here and then
        _amount=amount;
        _currency=currency;
    }
}

现在如果你需要从客户端发回这些,你不能轻易地直接在 DTO 对象中重新使用它们,除非你拥有的任何 DTO 映射系统(自定义 WebAPI 模型绑定器、Automapper 等)可以轻松地让您使用构造函数将 DTO 绑定到值对象...这对您来说可能是问题,也可能不是问题,它可能会变得混乱 :)

  • 尽管如此,我倾向于远离 "generic" DTO 对象,但请记住,在 UI 上你仍然想要一些 "Domain" 用于客户端代码(无论是网页上的 Javascript 还是 Form/Console 上的 C# 或其他)。另外,找到具有 Name/Value/Unit/Plus One Weird Property specific to that Value concept

  • 的特殊值对象往往只是时间问题
  • 处理此问题的唯一 "fool-proof"*** 方法是每个值对象一个 DTO;虽然这是额外的工作,但你不会真的出错——如果你有很多很多这样的值对象,你总是可以编写一个简单的 DTO 生成工具或使用 T4 模板为你生成它们,基于 public 值对象的属性。

***不能保证

DDD 是关于行为和明确表达意图的,其次是清楚地识别您要解决的问题的边界上下文(事务和组织边界)。这比您要求回答的 "structural" 问题的类型重要得多。

即从可能有 "Value Objects" 的 "Domain Entities" 开始,其中 "Domain Entities" 被映射为 "DTO" 到 show/be 在 UI 中编辑是关于你是如何构建事物的,这并没有说明用户在这个 UI 中试图实现什么,也没有说明组织需要做什么来响应这个(即真正的业务规则,例如奖励折扣,改变送货地址、推荐用户可能感兴趣的其他产品、更改计费货币等)。

从您的描述看来,您有一个域模型,它正在镜像 viewed/edited UI 上需要的内容。那有点"putting the horse behind the carriage"。现在你有很多 "tiers" 没有提供附加值,并且增加了很多复杂性。

让我试着解释一下我的意思,使用在 "Order" 和 "Money" 中提到的(简化的)示例。使用上述方法,尝试在屏幕上显示此内容可能涉及以下步骤:

  1. 读取给定 OrderId 的 "Order Entity" 及其相关 "Money" 值(可能在具有给定数量和单价的特定产品类型的订单行中)。这将需要一个带有多个连接的 SQL 语句(如果使用 SQL 数据库)。
  2. 以某种方式将这些中的每一个映射到镜像 "domain objects" 结构。
  3. 再次将这些映射到镜像 "DTO" 对象层次结构。
  4. 将这些 "DTO" 对象映射到 UI 中的 "View" 或 "ViewModel" 对象。

在这个例子中,有很多工作没有产生任何应该捕获和执行业务逻辑的模型的好处。

下一步,用户正在编辑 UI 中的字段。而且您必须以某种方式使用反向路由将其编组回您的域实体,并尝试从已更改的字段中推断用户的意图,然后对其应用业务规则。

例如,假设用户更改了订单项 "MoneyDTO" 上的货币。用户的意图是什么?将其设为新的结算货币并针对所有其他行项目也进行更改?这与业务规则有何关系?您是否需要查找汇率并更改所有订单项的 "Moneys"?波动较大的货币是否有不同的业务逻辑?您需要切换到有关增值税的新规则吗?

这些问题类型似乎与您的领域更相关,并且可能导致领域实体和服务的结构不同于 viewed/modified [=] 上的模型75=].

为什么不简单地将视图模型存储在您的数据库中(例如,作为 Json,这样它可以通过单个查询检索并直接呈现),这样您就不需要额外的转换层来将它显示给用户。此外,为什么不构造您的 UI 以揭示意图,并将其映射到要发送到您的域服务的命令。例如。 "change shipping address" 命令可能与组织的 "shipping" 限界上下文相关,"change billing currency" 与 "billing" 限界上下文相关。

此外,如果您用从您的域生成的域事件来补充它,表示 "has happened" 您将获得额外的好处。例如,"order line added" 事件可以由 "Additional Products A User Might Be Interested In" 服务获取,作为响应,为用户更新 UI 中的 "Suggested Products" 视图模型。

我建议您查看 CQRS 中的概念,将其作为处理此类问题的一种可能方法。作为一个非常基本的介绍和一些更详细的参考资料,您可以查看 Martin Fowler 对此的看法:http://martinfowler.com/bliki/CQRS.html