域驱动设计中的域对象是否应该仅是写操作?


13

我已经阅读了近两年的关于域驱动设计的文章,并且一直在谨慎地将一些概念引入我的日常工作中,或者至少计划了如何在域驱动设计中定期完成工作的计划。

我开始得出一个结论,尤其是在阅读了有关事件源和命令查询责任隔离(CQRS)的更多信息后,域对象可能仅用于写目的。更清楚地说,在我阅读的许多文档中,人们似乎巧妙地暗示了域对象负责进行以域为中心的操作/计算,验证,然后在那里主要是为持久化提供了一条途径存储库实现中提供的基础结构。尽管我喜欢这样的事实,因为它可以大大简化领域模型,因为它消除了公开状态的责任。

如果确实确实将域对象主要用作只写对象,那么这对我提出了一些问题,希望有人可以回答。

  1. 如何对具有setter的对象或修改对象状态但不提供向外公共接口以从诸如C#中的属性获取器读取状态的方法执行单元测试?仅仅为了使该对象可测试就可以公开状态吗?
  2. 如何向用户显示在域中完成的计算或运算的结果,而不必持久存储它们,然后将结果从持久存储中拉出到域的上下文之外?仅出于显示结果的目的而公开状态是否可以?

根据经验,唯一的属性获取器(获取访问器)应该是在域中也可写的属性吗?还是换句话说,只读属性应该唯一避免,因为它们仅出于读取目的存在,因此在实际的域模型中不发挥必要的作用?

相关资料:

  1. TDD,DDD和封装

Answers:


9

不确定公平地说,仍在不断发展的设计方法是否有“唯一的方法”答案。首先,尽管CQRS成员似乎是从受DDD影响的起点衍生而来的,但DDD和CQRS并不是同一个人。

DDD的思维方式有很多,其中大部分与正确定义的问题边界,利益相关者之间的交流以及系统之间的交互有关,而不必一定是代码中的特定实现,因此我不觉得太难-核心是美德。

您可能正在围绕是否应该更改域对象以及如何更改域对象以及域对象在整个系统中起什么作用等问题进行辩论。CQRS将系统分为读取和写入路径,因此可以得出这样的结论:在使用写入路径时,您实际上不需要读取访问权限。这样一来,您就可以针对某些域对象引发的事件以及其他对象消耗(处理)的事件进行读取。如果您回顾一下CQRS的历史,您会发现参数表明域对象不应具有setter,而应仅具有getter和单个“ handler”方法。这里的逻辑是,只有消耗事件才应导致状态更改,并且该更改完全由域对象在内部处理。

通过将更改结果视为单独的更改工件,将它们置于单独的扁平持久结构(例如表)中,然后像阅读有关系统当前和历史状态的报告一样阅读,就可以显示更改结果。例如,您可以通过提取需要读取的数据并将其保存到与系统的单个视图(例如屏幕)紧密映射的数据库表中来消耗事件。

如果您尝试使用这种样式,请意识到其他程序员可能不会熟悉这种方法,并且相对较少(但很有趣)的场景可以证明它是一种设计方法。

对于单元测试,有两种方法可能对您有用。首先,也是最自然的,是验证您希望看到的域对象引发的事件是否正确。域对象可能会引发一个通用的更改事件,其中包含有关更改的信息。您可以验证一下。您可以验证是否确实引发了该事件,而没有引发其他事件。

另一种方法是使用测试间谍,这些间谍在域对象上公开可读属性,以便您可以验证状态更改。例如,您可以从您的域对象继承,添加一些访问器以读取原本应封装的状态并验证其正确性。

再次,您感到困惑,因为这些方法令人困惑。如果您希望在编程中采用一些好主意,请首先选择多汁的工具。DDD边界和明确的角色是您与代码通信的思维方式的变化。CQRS至少表明读数据和写数据是可细分的操作。这种见解使您可以非常清楚地思考您需要呈现的数据的作用是什么,您真正需要多少,使用谁,需要多新鲜等等,等等。您不需要全面的事件源实现,可以在编码中采用更好的封装。您可以先关注对象内的原子操作,然后使用“告诉,不要问”对象接口设计方法,


1
+1用于从jucy位开始。另外:仅终止CQS(暂时跳过“事件”部分)可能是一个不错的起点。
cottsak

1

域驱动设计中的域对象是否应该仅是写操作?

CQRS可以一起DDD使用。


但是CQRS的“查询”部分是否只涉及查询域模型用于将更改写入模型的数据,还是可以用于查询可能向用户显示值的应用程序层的数据?我从某些人那里听到,DDD只是为了协调更改,除了将更改协调到域模型中的另一个对象之外,不应将其用于其他目的。一种决定或另一种决定将意味着根本不同的模型设计,即如果仅在域中使用数据,则暴露在域对象上的数据将受到不同的限制。
jpierson

0

您的域模型单元测试应检查是否为每个执行的命令引发了正确的域事件。您的域命令和捕获的事件可以被询问状态。

您还可以ToString()在域上覆盖命令和事件以提供状态的自动,人工可读的报告。

要回答第二个问题,要显示命令的结果,您应该安排将域事件“发布”到读取模型。


您能否详细说明一下在不使用事件源的情况下这是否仍然适用?
jpierson 2011年

1
没有活动来源,我的建议可能行不通。我不知道您的编码方式。对于理想情况下不公开任何属性的域对象,也许您可​​以显式实现一个测试接口,以公开要测试的属性。只有您的测试代码会“知道”将此类域对象转换为测试接口。
艾德·詹姆斯

谢谢你的建议。对于专门针对可测试性修改域类的想法,我感到有些不安,但如果您希望它可测试,那么我想这可能仍是域驱动设计中的那些灰色区域之一。另一个想法是,如果要通过同一接口公开稳定性和可测试性,那么至少您只引入一种基础结构依赖性,而不是两种。别人怎么看?
jpierson
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.