当您从控制器内的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("...");
它是由控制器映射的命令对象。值对象初始化将出现在命令处理程序方法中,并且它们会在值不正确的情况下抛出一些东西(初始化中的自我验证)。
我想我开始不那么烦恼,但其他一些意见也会受到欢迎。
答案 0 :(得分:4)
作为介绍,这是高度自以为是,我确信每个人对如何运作都有不同的想法。但我在这里的努力是概述一个策略,背后有一些很好的理由,这样你就可以进行自己的评估。
我个人的偏好是解析Controller中的所有内容并将结果发送给服务。这种方法有两个主要阶段,每个阶段都可以吐出错误条件:
当一堆string
从UI进来时,我认为尝试立即解释它们是有意义的。对于像int
和bool
这样的简单目标,这些转换非常简单,许多Web框架的模型绑定器会自动处理它们。
对于更复杂的对象(如自定义类),在此位置处理它仍然有意义,以便所有解析都在同一位置进行。如果你在一个提供模型绑定的框架中,大部分解析可能是自动完成的;如果没有 - 或者您正在组装一个更复杂的对象以发送到服务 - 您可以在Controller中手动完成。
失败情况如果解析失败(在"hello"
字段中输入int
或为7
输入bool
),则很容易将反馈发送到用户甚至不得不拨打该服务。
即使解析成功,仍然需要验证该条目是合法的,然后提交它。我更喜欢在提交之前立即处理服务级别的验证。这使得Controller负责解析并在代码中非常清楚地表明对提交的每个数据进行验证。
在执行此操作时,我们可以从服务层中消除辅助职责。没有必要让它解析对象 - 它的唯一目的是提交信息。
失败情况当验证失败时(有人在月球上输入地址,或者输入过去300年的出生日期),应该将失败报告给呼叫者(在这种情况下为控制器)。虽然用户可能没有区分解析失败和验证失败,但这对软件来说是一个重要的区别。
我每次都会尽可能地接受解析后的对象。如果你可以让其他人的框架处理这种转变,为什么不这样做呢?此外,物品越靠近用户界面,就越容易向用户提供关于他们正在做什么的快速反馈。
总的来说,将对象推向堆栈 会导致更大的耦合。但是,为特定域编写软件确实涉及与域紧密耦合,无论它是什么。如果更多的组件与整个域中无处不在的某些概念紧密耦合 - 或者至少与所调用的服务的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中直接使用业务对象时不会那么糟糕。