88

似乎没有太多细节的领域驱动设计的一部分是,您应该如何以及为什么应该将您的领域模型与您的界面隔离开来。我试图说服我的同事这是一个很好的做法,但我似乎没有取得太大进展......

他们在表示层和界面层中随心所欲地使用领域实体。当我向他们争辩说他们应该使用显示模型或 DTO 将域层与界面层隔离时,他们反驳说他们没有看到这样做的商业价值,因为现在你有一个 UI 对象要维护以及原始域对象。

所以我正在寻找一些具体的理由来支持这一点。具体来说:

  1. 为什么我们不应该在表示层中使用域对象?
    (如果答案很明显,“脱钩”,那么请解释为什么这在这种情况下很重要)
  2. 我们应该使用额外的对象或构造来将我们的领域对象与接口隔离吗?
4

14 回答 14

51

很简单,原因是实施和漂移之一。是的,您的表示层需要了解您的业务对象才能正确表示它们。是的,最初看起来这两种对象的实现之间有很多重叠。问题是,随着时间的推移,双方的事情都会增加。表示发生变化,表示层的需求演变为包括完全独立于业务层的事物(例如颜色)。同时,你的领域对象会随着时间而改变,如果你没有与你的接口进行适当的解耦,你就会冒着通过对你的业务对象进行看似良性的改变来搞砸你的接口层的风险。

就个人而言,我认为处理事情的最佳方式是通过严格执行的接口范式。也就是说,您的业务对象层公开了一个接口,这是可以与之通信的唯一方式;没有公开有关接口的实现细节(即域对象)。是的,这意味着您必须在两个位置实现域对象;你的接口层和你的BO层。但是,这种重新实现,虽然最初看起来像是额外的工作,但有助于执行解耦,这将在未来的某个时候节省大量的工作。

于 2009-05-04T18:28:00.190 回答
19

我自己也为此苦苦挣扎。在某些情况下,在演示文稿中使用 DTO 是有意义的。假设我想在我的系统中显示公司的下拉列表,我需要他们的 id 来绑定值。

好吧,而不是加载可能引用订阅或知道其他内容的 CompanyObject,我可以发回带有名称和 ID 的 DTO。恕我直言,这是一个很好的用途。

现在再举一个例子。我有一个代表估计的对象,这个估计可能由劳动力、设备等组成,它可能有很多由用户定义的计算,这些计算将所有这些项目汇总起来(每个估计可能因不同类型而异的计算)。为什么我必须对这个对象建模两次?为什么我不能简单地让我的 UI 枚举计算并显示它们?

我通常不使用 DTO 将我的域层与我的 UI 隔离。我确实使用它们将我的域层与我无法控制的边界隔离开来。有人将导航信息放在他们的业务对象中的想法是荒谬的,不要污染您的业务对象。

有人将验证放在他们的业务对象中的想法?好吧,我说这是一件好事。您的 UI 不应单独负责验证您的业务对象。您的业​​务层必须进行自己的验证。

为什么要将 UI 生成代码放在业务对象中?在我的情况下,我有单独的对象,它们从 UI 生成 UI 代码 seperatley。我有将我的业务对象呈现为 Xml 的分离对象,您必须分离图层以防止这种类型的污染的想法对我来说是如此陌生,因为您为什么还要将 HTML 生成代码放在业务对象中......

编辑 我想多了,在某些情况下,UI 信息可能属于域层。这可能会使你所谓的域层变得模糊,但我在一个多租户应用程序上工作,它在 UI 外观和功能工作流方面具有非常不同的行为。取决于各种因素。在这种情况下,我们有一个代表租户及其配置的域模型。他们的配置恰好包含 UI 信息(例如通用字段的标签)。

如果我必须设计我的对象以使其具有持久性,我是否还必须复制这些对象?请记住,如果您想添加一个新字段,现在您有两个地方可以添加它。如果您使用 DDD,这可能会引发另一个问题,这些都是持久实体域对象吗?我知道在我的例子中他们是。

于 2009-05-04T18:53:04.357 回答
16

这样做的原因与将 SQL 排除在 ASP/JSP 页面之外的原因相同。

如果您只保留一个域对象,用于表示层和域层,那么该对象很快就会变得单一。它开始包含 UI 验证代码、UI 导航代码和 UI 生成代码。然后,您很快就会在其上添加所有业务层方法。现在你的业务层和 UI 都混在一起了,都在领域实体层搞混了。

您想在另一个应用程序中重用那个漂亮的 UI 小部件吗?好吧,您必须使用这个名称、这两个模式和这 18 个表创建一个数据库。您还必须配置 Hibernate 和 Spring(或您选择的框架)来进行业务验证。哦,你还必须包括这85个其他不相关的类,因为它们在业务层中被引用,恰好在同一个文件中。

于 2009-05-04T18:28:27.707 回答
16

我不同意。

我认为最好的方法是从表示层中的域对象开始,直到这样做有意义。

与流行的看法相反,“域对象”和“值对象”可以愉快地共存于表示层。这是做到这一点的最佳方式 - 您可以从两个世界中受益,减少域对象的重复(和样板代码);以及跨请求使用值对象的剪裁和概念简化。

于 2009-05-14T06:58:49.033 回答
8

答案取决于您的应用程序的规模。


简单的 CRUD(创建、读取、更新、删除)应用程序

对于基本的 crud 应用程序,您没有任何功能。在实体之上添加 DTO 会浪费时间。它会增加复杂性而不增加可扩展性。

在此处输入图像描述


中等复杂的非 CRUD 应用程序

在这种规模的应用程序中,您将很少有实体具有真正的生命周期和一些与之相关的业务逻辑。

在这种情况下添加 DTO 是一个好主意,原因如下:

  • 表示层只能看到实体具有的字段的子集。你封装实体
  • 后端和前端之间没有耦合
  • 如果您在实体内部有业务方法,但在 DTO 中没有,那么添加 DTO 意味着外部代码不能破坏实体的状态。

在此处输入图像描述


复杂的企业应用

单个实体可能需要多种呈现方式。他们每个人都需要不同的字段集。在这种情况下,您会遇到与上一个示例相同的问题,并且需要控制每个客户端可见的字段数量。为每个客户端设置单独的 DTO 将帮助您选择应该可见的内容。

在此处输入图像描述

于 2017-06-12T08:00:54.923 回答
4

我们在服务器和 ui 上使用相同的模型。这是一种痛苦。我们总有一天要重构它。

问题主要是因为需要将域模型切割成更小的部分,以便能够在不引用整个数据库的情况下对其进行序列化。这使得在服务器上使用起来更加困难。缺少重要链接。有些类型也不是可序列化的,不能发送到客户端。例如“类型”或任何通用类。它们需要是非泛型的,并且类型需要作为字符串传输。这会为序列化生成额外的属性,它们是多余的和令人困惑的。

另一个问题是 UI 上的实体并不适合。我们正在使用数据绑定,并且许多实体具有许多仅用于 ui 目的的冗余属性。此外,实体模型中有许多“BrowsableAttribute”和其他。这真的很糟糕。

最后,我认为这只是哪种方式更容易的问题。可能有项目可以正常工作并且不需要编写另一个 DTO 模型。

于 2009-05-04T18:28:58.750 回答
3

大部分是关于依赖关系的。组织的核心功能结构有自己的功能需求,UI应该让人们可以修改和查看核心;但不应要求内核本身适应 UI。(如果它需要发生,这通常表明核心不是属性设计的。)

我的会计系统有一个结构和内容(和数据),应该模拟我公司的运营。无论我使用什么会计软件,这种结构都是真实存在的。(不可避免地,一个给定的软件包本身就包含结构和内容,但部分挑战是尽量减少这种开销。)

基本上一个人有工作要做。DDD 应该与工作的流程和内容相匹配。DDD 是关于尽可能完整和独立地明确所有需要完成的工作。然后,UI 希望有助于尽可能透明、尽可能高效地完成工作。

接口是关于为正确建模和不变的功能核心提供的输入和视图。

于 2009-05-04T18:30:26.907 回答
3

该死的,我发誓这就是坚持。

无论如何,这是同一件事的另一个例子:帕纳斯定律说模块应该保密,而这个秘密是可以改变的要求。(Bob Martin 有一个规则,它是另一个版本。)在这样的系统中,表示可以独立于domain改变。例如,一家公司维持欧元价格并在公司办公室使用法语,但希望以普通话文本显示美元价格。是一样的;演示文稿可以更改。因此,为了最大限度地减少系统的脆弱性——也就是说,为了实现需求的改变而必须改变的事物的数量——你将关注点分开。

于 2009-05-04T18:45:25.297 回答
2

您的演示文稿可能会引用您的域层,但不应直接从您的 ui 绑定到您的域对象。域对象不适合 UI 使用,因为如果设计得当,它们通常基于行为而不是数据表示。UI 和 Domain 之间应该有一个映射层。MVVM 或 MVP 是一个很好的模式。如果您尝试直接将您的 UI 绑定到域,您可能会给自己带来很多麻烦。他们有两个不同的目的。

于 2009-06-02T01:47:14.593 回答
1

也许您没有在足够宽泛的术语中概念化 UI 层。考虑多种形式的响应(网页、语音响应、印刷信件等)和多种语言(英语、法语等)。

现在假设电话呼入系统的语音引擎运行在与运行网站的计算机(可能是 Windows)完全不同类型的计算机(例如 Mac)上。

当然很容易陷入“在我公司我们只关心英语,在 LAMP(Linux、Apache、MySQL 和 PHP)上运行我们的网站,每个人都使用相同版本的 Firefox”的陷阱。但是 5 年或 10 年后呢?

于 2009-05-04T18:35:39.593 回答
1

另请参阅下面的“层之间的数据传播”部分,我认为这提出了令人信服的论点:

http://galaxy.andromda.org/docs/andromda-documentation/andromda-getting-started-java/java/index.html

于 2009-05-04T21:48:07.417 回答
1

在使用视图时,借助“ Value Injecter ”等工具和表示层中的“Mappers”概念,可以更容易地理解每段代码。如果您有一点代码,您不会立即看到优势,但是当您的项目越来越大时,您会很高兴在使用视图时不必进入服务的逻辑,存储库以了解视图模型。View Model 是浩瀚反腐层的另一位卫士,在长期项目中价值连城。

我认为使用视图模型没有优势的唯一原因是,如果您的项目足够小且足够简单,可以将视图直接绑定到模型的每个属性。但是如果在未来,需求变化和视图中的一些控件将不会绑定到模型并且您没有视图模型概念,您将开始在许多地方添加补丁,并且您将开始拥有遗留代码你不会欣赏的。当然,您可以进行一些重构以在 view-viewmodel 中转换您的视图模型并遵循 YAGNI 原则,如果您不需要它,则不要添加代码,但对于我自己来说,这是我必须遵循的最佳实践来添加表示层仅公开视图模型对象。

于 2012-07-02T12:49:02.233 回答
1

这是一个真实的示例,说明为什么我认为将域实体与视图分开是一种很好的做法。

几个月前,我创建了一个简单的 UI,通过一系列 3 个仪表显示土壤样本中氮、磷和钾的值。每个仪表都有红色、绿色和红色部分,即每个组件的含量可能太少或太多,但中间有一个安全的绿色水平。

没有多想,我为我的业务逻辑建模以提供这 3 种化学成分的数据和一个单独的数据表,其中包含有关 3 种情况中每种情况的可接受水平的数据(包括正在使用的测量单位,即摩尔或百分比)。然后我对我的 UI 进行建模以使用一个非常不同的模型,这个模型关注量规标签、值、边界值和颜色。

这意味着当我后来不得不展示 12 个组件时,我只是将额外的数据映射到 12 个新的仪表视图模型中,它们就会出现在屏幕上。这也意味着我可以轻松地重用仪表控件并让它们显示其他数据集。

如果我将这些仪表直接耦合到我的域实体中,我将没有上述任何灵活性,并且将来的任何修改都会令人头疼。在 UI 中对日历进行建模时,我遇到了非常相似的问题。如果有 10 人以上的参加者要求日历约会变为红色,则处理此问题的业务逻辑应保留在业务层中,并且 UI 中的所有日历都需要知道,是否已指示它变红,应该不需要知道为什么。

于 2016-03-14T09:18:47.687 回答
-1

在通用语义和特定领域语义之间添加额外映射的唯一合理原因是,您拥有(访问)现有的代码(和工具)主体,这些代码(和工具)基于不同于您的领域语义的通用(但可映射)语义。

当与一组正交的功能域框架(如 ORM、GUI、工作流等)结合使用时,域驱动设计效果最佳。永远记住,只有在外层邻接关系中才需要暴露域语义。通常这是前端 (GUI) 和持久后端 (RDBM,ORM)。任何有效设计的中间层都可以而且应该是域不变的。

于 2009-05-04T18:38:15.330 回答