DDD:在控制器内部使用Value Objects?

时间:2015-03-16 14:28:24

标签: domain-driven-design value-objects

当您从控制器内的UI接收字符串格式的参数时,是否直接将字符串传递给应用程序服务(或命令)?

或者,您是否从控制器内的字符串创建值对象?

new Command(new SomeId("id"), Weight.create("80 kg"), new Date())

new Command("id", "80 kg", new Date())
new Command("id", "80", "kg", new Date())

也许这不重要,但它困扰我。

问题是,我们是否应该将域中的值对象耦合到控制器(内部)?

想象一下,如果您没有在应用程序层和表示层之间建立Web(如android活动或swing),您是否会在UI中推动使用值对象?


另一件事,您是否将值对象序列化/反序列化为字符串?

Weight weight = Weight.create("80 kg"); 
weight.getValue().equals(80.0);
weight.getUnit().equals(Unit.KILOGRAMS);
weight.toString().equals("80 kg");

在将字符串传递给命令的情况下,我宁愿通过" 80 kg"而不是" 80"和" kg"。

如果问题不相关或有趣,请抱歉。

谢谢。


更新

我在搜索有关完全不同的主题的信息时遇到了这个帖子:Value Objects in CQRS - where to use

他们似乎更喜欢原语或DTO,并将VO保留在域内。

我还看过V. Vernon的书(实施DDD),它在第14章(第522页)中谈到(完全是-_-)

我注意到他在没有任何DTO的情况下使用命令。

someCommand.setId("id");
someCommand.setWeightValue("80");
someCommand.setWeightUnit("kg");
someCommand.setOtherWeight("80 kg");
someCommand.setDate("17/03/2015 17:28:35");
someCommand.setUserName("...");
someCommand.setUserAttribute("...");
someCommand.setUserOtherAttributePartA("...");
someCommand.setUserOtherAttributePartB("...");

它是由控制器映射的命令对象。值对象初始化将出现在命令处理程序方法中,并且它们会在值不正确的情况下抛出一些东西(初始化中的自我验证)。

我想我开始不那么烦恼,但其他一些意见也会受到欢迎。

2 个答案:

答案 0 :(得分:4)

作为介绍,这是高度自以为是,我确信每个人对如何运作都有不同的想法。但我在这里的努力是概述一个策略,背后有一些很好的理由,这样你就可以进行自己的评估。

传递字符串或解析?

我个人的偏好是解析Controller中的所有内容并将结果发送给服务。这种方法有两个主要阶段,每个阶段都可以吐出错误条件:

1。尝试解析

当一堆string从UI进来时,我认为尝试立即解释它们是有意义的。对于像intbool这样的简单目标,这些转换非常简单,许多Web框架的模型绑定器会自动处理它们。

对于更复杂的对象(如自定义类),在此位置处理它仍然有意义,以便所有解析都在同一位置进行。如果你在一个提供模型绑定的框架中,大部分解析可能是自动完成的;如果没有 - 或者您正在组装一个更复杂的对象以发送到服务 - 您可以在Controller中手动完成。

失败情况

如果解析失败(在"hello"字段中输入int或为7输入bool),则很容易将反馈发送到用户甚至不得不拨打该服务。

2。验证并提交

即使解析成功,仍然需要验证该条目是合法的,然后提交它。我更喜欢在提交之前立即处理服务级别的验证。这使得Controller负责解析并在代码中非常清楚地表明对提交的每个数据进行验证。

在执行此操作时,我们可以从服务层中消除辅助职责。没有必要让它解析对象 - 它的唯一目的是提交信息。

失败情况

当验证失败时(有人在月球上输入地址,或者输入过去300年的出生日期),应该将失败报告给呼叫者(在这种情况下为控制器)。虽然用户可能没有区分解析失败和验证失败,但这对软件来说是一个重要的区别。

将值对象推送到UI?

我每次都会尽可能地接受解析后的对象。如果你可以让其他人的框架处理这种转变,为什么不这样做呢?此外,物品越靠近用户界面,就越容易向用户提供关于他们正在做什么的快速反馈。

关于耦合的注意事项

总的来说,将对象推向堆栈 会导致更大的耦合。但是,为特定域编写软件确实涉及与紧密耦合,无论它是什么。如果更多的组件与整个域中无处不在的某些概念紧密耦合 - 或者至少与所调用的服务的API接触点紧密耦合 - 我不会发现架构完整性或灵活性发生任何真正的降低。

解析一个大字符串或组件?

通常,将整个string传递到Parse()方法以进行排序通常是最简单的。以"80 kg"

为例
  • "80 kg""120 lbs"可能都是有效的重量输入
  • 如果您将string传递给Parse()方法,那么无论如何它可能会做一些相当沉重的提升。期望它根据空间分割string并不是一种霸道。
  • 调用Weight.create(inputString)要比inputString分割" "要容易得多,然后拨打Weight.create(split[0], split[1])
  • 维护单字符串输入Parse()功能也更容易。如果出现了一些新要求Weight类必须支持磅数和盎司,则新的有效输入可能为"120 lbs 6 oz"。如果您正在拆分输入,那么现在需要四个参数。如果它完全封装在Parse()逻辑中,那么外部消费者就没有负担。这使代码更具可扩展性和灵活性。

答案 1 :(得分:0)

DTO和VO之间的区别在于DTO没有行为,它是一个简单的容器,用于将数据从组件传递到组件。此外,您很少需要比较两个DTO,它们通常是短暂的。

Value Object可以有行为。两个VO通过值而不是引用进行比较,这意味着例如具有相同数据但不同对象实例的两个Address值对象将是相等的。这很有用,因为它们通常以某种形式存在,并且有更多时间来比较它们。

事实证明,在DDD应用程序中,VO将在您的域层中更频繁地被声明和使用,因为它们属于域的普遍存在的语言并且由于关注点的分离。它们有时可以在Application层中进行操作,但通常不会在UI层和Application之间发送。我们使用DTO代替。

当然,这是debatable,并且很大程度上取决于您选择构建应用程序的层。可能会出现这样的情况,即将层次结构分解为2层将是有益的,并且在UI中直接使用业务对象时不会那么糟糕。