在我的域中,每个域实体可能有许多值对象。我创建了价值对象来表示货币、重量、计数、长度、体积、百分比等。
每个值对象都包含一个数值和一个度量单位。例如,货币包含货币价值和货币(美元、欧元…),重量包含数值和重量单位(公斤、磅…)
在用户界面中,这些也并排显示:字段名称、其值及其附带的单位,通常在属性面板中。域实体具有向UI公开的等效DTO。
我一直在寻找将DTO中的值对象传输到UI的最佳方式。
- 我是否只是将特定值对象作为DTO的一部分公开
- 我是否公开一个通用的"值对象"——在DTO中提供名称/值/单位的等价物
- 我是否在DTO中将其拆分为单独的名称/值/单元成员,只是为了在UI中重新组装它们
- 我是将它们作为KeyValuePair还是Tuple在DTO内传输
- 还有别的吗
我仔细搜索了一下,但似乎没有其他问题能完全解决这个问题。非常感谢您的建议!
编辑:在UI中,值和单位都可以更改并发送回域进行更新。
如果这些是单向传输,我倾向于同意debuggr在上面的评论;Value对象并不是真正的域对象-它们没有可以改变其状态的行为,因此在许多方面,它们只是专门的"位桶",因为您可以在不丢失上下文的情况下串行化它们。
然而;如果你遵循了DDD实践(或者如果你的后端使用多线程等),那么你的Value对象是不可变的,也就是说,它们可能看起来像这样:
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绑定到Value Object。。。这对你来说可能是个问题,也可能不是个问题,它可能会变得一团糟:)
不过,对于这样的事情,我倾向于远离"通用"DTO对象,请记住,在UI上,您仍然希望客户端代码使用一些"域"的外观(无论是网页上的Javascript还是窗体/控制台上的C#,或者其他什么)。此外,发现具有
Name/Value/Unit/Plus One Weird Property specific to that Value concept
的异常Value对象往往只是时间问题唯一的"傻瓜式"***处理方法是每个Value对象一个DTO;尽管这是额外的工作,但你不会真的出错——如果你有很多这样的Value对象,你总是可以根据Value对象的公共属性编写一个简单的DTO生成工具或使用T4模板为你生成它们。
***不能保证
DDD是关于行为和明确表达意图的,其次是明确识别您试图解决的问题的有界上下文(事务和组织边界)。这比你要求回答的"结构性"问题要重要得多。
也就是说,从可能有"价值对象"的"域实体"开始,"领域实体"被映射为"DTO",以在UI中显示/编辑,这是一个关于你如何构建事物的声明,没有说明用户试图在这个UI中实现什么,也没有要求组织对此做什么(即真实的商业规则,如授予折扣、更改发货地址、推荐用户可能感兴趣的其他产品、更改计费货币等)。
从您的描述中可以看出,您有一个域模型,该模型反映了需要在UI上查看/编辑的内容。这有点"把马放在马车后面"。现在有很多"层",它们不提供附加值,而且增加了很多复杂性。
让我试着用一个(简化的)例子来解释我的意思,这个例子是关于"订单"one_answers"金钱"的。使用上面提到的方法,试图在屏幕上显示这一点可能需要以下步骤:
- 阅读给定OrderId的"订单实体"及其相关的"货币"值(可能在具有给定数量和单价的特定产品类型的订单行中)。这将需要一个带有多个联接的SQL语句(如果使用SQL DB)
- 以某种方式将其中的每一个映射到镜像的"域对象"结构
- 再次将这些映射到镜像"DTO"对象层次结构
- 将这些"DTO"对象映射到UI中的"View"或"ViewModel"对象
在这个例子中,大量的工作并没有产生任何好处,因为有一个模型应该捕获和执行业务逻辑。
现在,作为下一步,用户将在UI中编辑字段。您必须以某种方式使用反向路由将其封送回您的域实体,并尝试从更改的字段推断用户的意图,然后将业务规则应用于此。
例如,假设用户更改行项目的"MoneyDTO"上的货币。用户的意图是什么?是否将其作为新的计费货币,并将其更改为所有其他行项目?这与商业规则有何关联?您需要查找汇率并更改所有行项目的"Moneys"吗?对于波动较大的货币,是否有不同的商业逻辑?您需要改用有关增值税的新规则吗?
这些类型的问题似乎与您的域更相关,可能会导致域实体和服务的结构与在UI上查看/修改的模型不同。
为什么不简单地将视图模型存储在数据库中(例如,作为Json,这样就可以通过单个查询检索并直接呈现),这样就不需要额外的翻译层来向用户显示它了。此外,为什么不构建UI以显示意图,并将其映射到要发送到域服务的命令。例如,"更改发货地址"命令可能与组织的"发货"受限上下文相关,"更改记账货币"与"记账"受限上下文有关。
此外,如果你用从你的域生成的域事件来补充这一点,表示"已经发生"的事情,你会获得额外的好处。例如,"添加订单行"事件可以由"用户可能感兴趣的其他产品"服务接收,作为响应,该服务会更新用户界面中的"建议产品"视图模型。
我建议你看看CQRS中的概念,作为处理这些类型问题的一种可能方法。作为一个非常基本的介绍和一些更详细的参考,你可以看看马丁·福勒对此的看法:http://martinfowler.com/bliki/CQRS.html