为什么应将域实体与表示层隔离?


85

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

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

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

  1. 为什么我们不应该在表示层中使用域对象?
    (如果答案很明显,那就是“解耦”,那么请解释一下为什么在这种情况下如此重要)
  2. 我们是否应该使用其他对象或构造将我们的域对象与接口隔离?

这个问题应该在Wiki中。
Syed Tayyab Ali

@ m4bwav-应该是一个Wiki,因为它的措词方式是邀请讨论,而不是一个正确的答案。
罗布·艾伦,2009年

1
@ m4bwav:我认为您提出的问题更多是作为一个观点而不是一个真正的问题……我试图纠正这一问题(您可能希望对其进行进一步的编辑),但是请注意,如果没有适当的注意,这似乎会拖钓。
Shog9年

5
好的,备份,我在问一个合理的问题,这将如何冒犯任何人?我的目标是谁?
马克·罗杰斯

@ m4bwav:您的目标是稻草人。您在问题中与之讨论的“人数众多”。
Shog9年

Answers:


48

很简单,原因是实现和偏离之一。是的,您的表示层需要了解您的业务对象,以便能够正确表示它们。是的,最初看起来这两种对象的实现之间有很多重叠。问题是,随着时间的流逝,双方都在增加东西。表示发生了变化,表示层的需求也随之发展,包括了与业务层完全独立的事物(例如颜色)。同时,您的域对象会随着时间而变化,如果您没有从界面中进行适当的解耦,则可能会通过对业务对象进行看似良性的更改来冒充破坏接口层的风险。

我个人认为,处理问题的最佳方法是通过严格执行的接口范式;也就是说,您的业务对象层公开了一个接口,这是可以与之通信的唯一方式;没有公开有关该接口的实现细节(即域对象)。是的,这意味着您必须在两个位置实现域对象。您的界面层和BO层。但是,重新实现虽然最初看起来似乎是多余的工作,但有助于实现去耦,这将节省将来某个时间的大量工作。


2
“在两个位置实现域对象”是什么意思?
jlembke

10
这对我来说似乎很愚蠢。为什么现在要做额外的工作以使将来可以节省工作?十分之九的工作量,您将无需进行任何更改即可节省工作量。
哔哔声,

13
@LuckyLindy:每100次中有99次(实际上更多),不必系安全带以免受伤。但是,在某个情况下,当我真正需要它时,它可能(使我)免于丧命或重伤。一盎司的预防值得一磅的治疗。我怀疑您有更多经验后,对此的看法会改变。
Paul Sonier

19

我自己为此苦苦挣扎。在某些情况下,DTO在演示中有意义。假设我要在系统中显示“公司”下拉列表,并且需要它们的ID来绑定值。

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

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

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

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

为什么将UI生成代码放在busienss对象中?在我的情况下,我有单独的对象,这些对象从UI生成UI代码seperatley。我有将我的业务对象呈现为Xml的独立对象,为了防止这种类型的污染,您必须分开各层的想法对我来说太陌生了,因为您为什么还要在业务对象中放入HTML生成代码...

编辑 我认为,UI信息可能属于域层。这可能会覆盖您所谓的域层,但是我在一个多租户应用程序上工作,该应用程序在UI外观和功能工作流程上具有截然不同的行为。取决于各种因素。在这种情况下,我们有一个表示租户及其配置的域模型。它们的配置恰好包含UI信息(例如,通用字段的标签)。

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


不同承租人的标签是否会为每个承租人指示不同的普遍语言?我认为需要有一个元模型的概念,其中在具有解释层的租户之间共享一个域,以解释他们对元模型的解释。
凯尔

16

出于相同的原因将SQL保留在ASP / JSP页面之外也是这样做的。

如果只保留一个域对象供在表示和域层中使用,则该对象很快就会变得单一。它开始包括UI验证代码,UI导航代码和UI生成代码。然后,您很快将在此之上添加所有业务层方法。现在,您的业务层和UI混合在一起,并且它们都在域实体层四处乱搞。

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


13

我不同意。

我认为最好的方法是从您的表示层中的域对象开始,直到它可以使您做出其他选择为止。

与流行的看法相反,“域对象”和“值对象”可以很高兴地在表示层中共存。这是最好的方法-您可以从两个方面受益,而可以减少域对象的重复(和样板代码);以及跨请求使用价值对象的定制和概念简化。


感谢您的输入,我知道您来自哪里。虽然我并不是说这不是创建成功项目的另一种无限方式,但它似乎与“领域驱动设计”风格背道而驰,“领域驱动设计”风格适用于难以维护的较大和更复杂的项目长期来说。
马克·罗杰斯

不,这是错误的,这就是为什么这么多的站点最终容易受到sql注入攻击的原因。
雷米(Remi)

7

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


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

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

在此处输入图片说明


中等复杂的非CRUD应用

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

出于以下几个原因,在这种情况下添加DTO是一个好主意:

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

在此处输入图片说明


复杂的企业应用

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

在此处输入图片说明


4

我们在服务器和ui中使用相同的模型。这是一个痛苦。我们必须重构它的一天。

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

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

最后,我认为这只是哪种方法更简单的问题。在某些项目中,可能工作得很好并且不需要编写其他DTO模型。


2
如果要使用数据绑定,请运行linq查询并绑定到匿名类型。这使您可以展平并更改层次结构。您还可以用它很好地实现过滤和排序。
2009年

@Josh:感谢您的建议。这可能部分起作用。我本人不是GUI程序员,也不参与GUI概念。问题在于处理数据并将其发送回服务器的情况。
Stefan Steinegger,2009年

3

大多数情况下,它与依赖性有关。组织的核心功能结构具有自己的功能要求,用户界面应使人们能够修改和查看该核心;但是不需要核心本身来容纳UI。(如果需要发生,通常表明核心不是属性设计的。)

我的会计系统的结构和内容(和数据)应该用来模拟公司的运作。不管我使用什么会计软件,该结构都是真实存在的。(一个给定的软件包不可避免地要包含结构和内容,但是要使其负担最小化是其中的部分挑战。)

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

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


3

该死,我发誓坚持不懈。

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


2

您的演示文稿可能引用了您的域层,但是不应直接将用户界面绑定到域对象。域对象不是用于UI的,因为如果设计恰当,它们通常是基于行为而不是数据表示的。UI和域之间应该有一个映射层。MVVM或MVP是一个很好的模式。如果您尝试直接将UI绑定到域,则probalby会让您自己头痛。他们有两个不同的目的。


1

也许您没有足够广泛地概念化UI层。用多种形式的响应(网页,语音响应,打印的信件等)和多种语言(英语,法语等)思考。

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

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



1

借助“价值注入器”之类的工具”之以及表示层在处理视图时的”概念,可以更轻松地理解每段代码。如果您只有一点点代码,您将不会立即看到优点,但是当您的项目越来越多时,您将很高兴在使用视图时不必输入服务逻辑,仓库以了解视图模型。View Model是广阔的反腐败世界中的另一个守护者,在一个长期项目中,它值得在黄金中占有一席之地。

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


1

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

几个月前,我创建了一个简单的用户界面,通过一系列3个量规来显示土壤样品中氮,磷和钾的值。每个量规都有一个红色,绿色和红色部分,也就是说,每个分量可以太少或太多,但是中间有一个安全的绿色水平。

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

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

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


-1

在通用语义和特定于领域的语义之间添加其他映射的唯一明智的原因是,您可以(访问)现有的代码本体(和工具),它们基于与您的域语义不同的通用(但可映射)语义。

与正交的功能域框架集(例如ORM,GUI,工作流等)结合使用时,域驱动的设计效果最好。始终记住,仅在外层邻接中才需要公开域语义。通常,这是前端(GUI)和持久后端(RDBM,ORM)。任何有效设计的中间层都可以并且应该是域不变的。


第1段:不要创建不必要的抽象(例如,可重用组件),除非您实际在不同的应用程序中共享它们。第2段:我想知道通用GUI如何在这么多不同的域中工作。备注:这个行业是如此破碎,甚至不再有趣了……
alphazero
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.