为啥我应该将我的域实体与我的表示层隔离?
Posted
技术标签:
【中文标题】为啥我应该将我的域实体与我的表示层隔离?【英文标题】:Why should I isolate my domain entities from my presentation layer?为什么我应该将我的域实体与我的表示层隔离? 【发布时间】:2010-10-23 16:55:15 【问题描述】:领域驱动设计中似乎没有太多细节的部分是,您应该如何以及为什么应该将您的领域模型与您的界面隔离开来。我试图说服我的同事这是一个很好的做法,但我似乎没有取得太大进展......
他们在表示层和界面层中随心所欲地使用域实体。当我向他们争辩说他们应该使用显示模型或 DTO 将领域层与界面层隔离时,他们反驳说他们没有看到这样做的商业价值,因为现在你有一个 UI 对象要维护以及原始域对象。
所以我正在寻找一些具体的理由来支持这一点。具体来说:
-
为什么我们不应该在表示层中使用域对象?
(如果答案很明显,“解耦”,那么请解释为什么这在这种情况下很重要)
我们是否应该使用额外的对象或构造来将我们的域对象与接口隔离开来?
【问题讨论】:
这个问题应该在wiki里。 @m4bwav - 它应该是一个 wiki,因为它的措辞是为了邀请讨论而不是一个正确的答案。 @m4bwav:我认为你的问题更像是一个观点而不是一个真正的问题......我试图纠正这个问题(你可能想进一步编辑它),但请注意如果没有适当的照顾,这可能看起来像是在拖钓。 好的,备份,我在问一个合理的问题,这会如何冒犯任何人?我的目标是谁? @m4bwav:你的目标是你的稻草人。您在问题中讨论此问题的“大量人”。 【参考方案1】:很简单,原因是实施和漂移之一。是的,您的表示层需要了解您的业务对象才能正确表示它们。是的,最初看起来这两种对象的实现之间有很多重叠。问题是,随着时间的推移,双方的事情都会增加。表示发生变化,表示层的需求演变为包括完全独立于业务层的事物(例如颜色)。同时,您的领域对象会随着时间而变化,如果您没有与接口进行适当的解耦,您可能会因对业务对象进行看似良性的更改而搞砸接口层。
就我个人而言,我认为处理事物的最佳方式是通过严格执行的接口范式;也就是说,您的业务对象层公开了一个接口,这是可以与之通信的唯一方式;没有公开有关接口的实现细节(即域对象)。是的,这意味着您必须在两个位置实现域对象;你的接口层和你的BO层。但是,这种重新实现虽然最初看起来像是额外的工作,但有助于加强解耦,这将在未来的某个时候节省大量的工作。
【讨论】:
“在两个位置实现域对象”是什么意思? 这对我来说似乎很愚蠢。为什么现在的额外工作可能会节省将来的工作? 10 次中有 9 次您永远不需要进行可以节省“吨”工作的更改。 @LuckyLindy:100 次中有 99 次(实际上更多),不需要系安全带来防止我受伤。但是,在我真正需要它的一种情况下,它(可能)会阻止我被杀或重伤。一盎司的预防胜过一磅的治疗。我怀疑在您获得更多经验后,您对此的看法会有所改变。【参考方案2】:我自己也为此苦苦挣扎。在某些情况下,在演示文稿中使用 DTO 是有意义的。假设我想在我的系统中显示公司的下拉列表,我需要他们的 id 来绑定值。
好吧,我可以发送回一个带有名称和 id 的 DTO,而不是加载可能引用订阅或知道其他内容的 CompanyObject。这是一个很好的使用恕我直言。
现在再举一个例子。我有一个代表估计的对象,这个估计可能由人工、设备等组成,它可能有很多由用户定义的计算,这些计算将所有这些项目汇总起来(每个估计可能因不同类型而异的计算)。为什么我必须对这个对象建模两次?为什么我不能简单地让我的 UI 枚举计算并显示它们?
我通常不使用 DTO 将我的域层与我的 UI 隔离。我确实使用它们将我的域层与我无法控制的边界隔离开来。有人将导航信息放在他们的业务对象中的想法是荒谬的,不要污染您的业务对象。
有人将验证放在他们的业务对象中的想法?好吧,我说这是一件好事。您的 UI 不应单独负责验证您的业务对象。您的业务层必须进行自己的验证。
为什么要将 UI 生成代码放在业务对象中?在我的情况下,我有单独的对象,它们从 UI 生成 UI 代码 seperatley。我有将我的业务对象呈现为 Xml 的分离对象,你必须分离你的层以防止这种类型的污染的想法对我来说是如此陌生,因为你为什么还要将 html 生成代码放在业务对象中......
编辑 我想多了,在某些情况下 UI 信息可能属于域层。这可能会使你所谓的域层变得模糊,但我在一个多租户应用程序上工作,它在 UI 外观和功能工作流方面具有非常不同的行为。取决于各种因素。在这种情况下,我们有一个代表租户及其配置的域模型。他们的配置恰好包含 UI 信息(例如通用字段的标签)。
如果我必须设计我的对象以使其具有持久性,我是否还必须复制这些对象?请记住,如果您想添加一个新字段,现在您有两个地方可以添加它。如果您使用 DDD,这可能会引发另一个问题,这些都是持久实体域对象吗?我知道在我的例子中他们是。
【讨论】:
不同租户的不同标签难道不会表明每个租户使用不同的通用语言吗?我认为需要有一个元模型的概念,其中一个域在租户之间共享,并有一个翻译层来解释他们对元模型的解释。【参考方案3】:您这样做的原因与您将 SQL 排除在 ASP/JSP 页面之外的原因相同。
如果您只保留一个域对象,用于表示层和域层,那么这个对象很快就会变得单一。它开始包含 UI 验证代码、UI 导航代码和 UI 生成代码。然后,您很快就会在其上添加所有业务层方法。现在你的业务层和 UI 都混在一起了,都在领域实体层搞混了。
您想在另一个应用程序中重复使用那个漂亮的 UI 小部件吗?好吧,您必须使用这个名称、这两个模式和这 18 个表创建一个数据库。您还必须配置 Hibernate 和 Spring(或您选择的框架)来进行业务验证。哦,你还必须包含这 85 个其他不相关的类,因为它们在业务层中被引用,恰好在同一个文件中。
【讨论】:
【参考方案4】:我不同意。
我认为最好的方法是从表示层中的域对象开始,直到这样做有意义。
与流行的看法相反,“域对象”和“值对象”可以愉快地共存于表示层。这是做到这一点的最佳方式——您将获得两全其美的好处,减少域对象的重复(和样板代码);以及跨请求使用值对象的剪裁和概念简化。
【讨论】:
感谢您的意见,我知道您来自哪里。虽然我并不是说这不是创建成功项目的另一种无限方法,但它似乎与“领域驱动设计”风格背道而驰,后者适用于更难维护的更大、更复杂的项目从长远来看。 不,这是错误的,这也是为什么这么多网站最终容易受到 sql 注入攻击的原因。【参考方案5】:答案取决于您的应用程序的规模。
简单的 CRUD(创建、读取、更新、删除)应用程序
对于基本的 crud 应用程序,您没有任何功能。在实体之上添加 DTO 会浪费时间。它会增加复杂性而不增加可扩展性。
中等复杂的非 CRUD 应用程序
在这种规模的应用程序中,您将很少有实体具有真正的生命周期以及与之关联的一些业务逻辑。
在这种情况下添加 DTO 是一个好主意,原因如下:
表示层只能看到实体拥有的字段子集。您封装实体 后端和前端之间没有耦合 如果您在实体内部有业务方法,但在 DTO 中没有,那么添加 DTO 意味着外部代码不会破坏实体的状态。复杂的企业应用
单个实体可能需要多种呈现方式。他们每个人都需要不同的字段集。在这种情况下,您会遇到与上一个示例相同的问题,并且需要控制每个客户端可见的字段数量。 为每个客户端设置单独的 DTO 将帮助您选择应该显示的内容。
【讨论】:
【参考方案6】:我们在服务器和用户界面上使用相同的模型。这是一种痛苦。总有一天我们必须重构它。
问题主要是因为领域模型需要被切割成更小的部分,以便能够在不引用整个数据库的情况下对其进行序列化。这使得在服务器上使用起来更加困难。缺少重要链接。有些类型也不是可序列化的,不能发送到客户端。例如“类型”或任何通用类。它们需要是非泛型的,并且类型需要作为字符串传输。这会为序列化生成额外的属性,它们是多余且令人困惑的。
另一个问题是 UI 上的实体并不适合。我们正在使用数据绑定,并且许多实体具有许多仅用于 ui 目的的冗余属性。此外,实体模型中有许多“BrowsableAttribute”和其他。这真的很糟糕。
最后,我认为这只是哪种方式更容易的问题。可能有项目可以正常工作,而无需编写另一个 DTO 模型。
【讨论】:
如果您要使用数据绑定,请运行 linq 查询并绑定到匿名类型。这使您可以展平和更改层次结构。你也可以用它很好地实现过滤和排序。 @Josh:感谢您的建议。这可能会部分起作用。我自己不是 GUI 程序员,也没有过多地参与 GUI 概念。问题将出现在数据被操纵并发送回服务器的情况下。【参考方案7】:大部分是关于依赖的。组织的核心功能结构有自己的功能需求,UI应该让人们可以修改和查看核心;但不应要求内核本身适应 UI。 (如果需要发生,通常表明核心不是属性设计的。)
我的会计系统有一个结构和内容(和数据),应该用来模拟我公司的运营。无论我使用什么会计软件,这种结构都是真实存在的。 (一个给定的软件包不可避免地会包含其自身的结构和内容,但部分挑战是尽量减少这种开销。)
基本上一个人有工作要做。 DDD 应该与工作的流程和内容相匹配。 DDD 是关于尽可能完整和独立地明确所有需要完成的工作。然后,UI 希望有助于尽可能透明、尽可能高效地完成工作。
接口是关于为正确建模和不变的功能核心提供的输入和视图。
【讨论】:
【参考方案8】:该死,我发誓这说的是坚持。
无论如何,这又是同一件事的另一个例子:帕纳斯定律说模块应该保密,而这个秘密是可以改变的要求。 (Bob Martin 有一个规则,它是另一个版本。)在这样的系统中,presentation 可以独立于 domain 改变。例如,一家公司维持欧元价格并在公司办公室使用法语,但希望以普通话文本显示美元价格。 域是一样的;演示文稿可以更改。因此,为了最大限度地减少系统的脆弱性(即为实现需求更改而必须更改的事物的数量),您需要分离关注点。
【讨论】:
【参考方案9】:您的演示文稿可能引用您的域层,但不应直接从您的用户界面绑定到您的域对象。域对象不适合 UI 使用,因为如果设计得当,它们通常是基于行为而不是数据表示。 UI 和 Domain 之间应该有一个映射层。 MVVM 或 MVP 是一个很好的模式。如果您尝试直接将您的 UI 绑定到域,您可能会给自己带来很多麻烦。它们有两个不同的目的。
【讨论】:
【参考方案10】:也许您对 UI 层的概念化不够宽泛。考虑多种形式的响应(网页、语音响应、印刷信件等)和多种语言(英语、法语等)。
现在假设电话呼入系统的语音引擎运行在与运行网站的计算机(可能是 Windows)完全不同类型的计算机(例如 Mac)上。
当然很容易掉入陷阱“在我公司,我们只关心英语,在 LAMP(Linux、Apache、mysql 和 php)上运行我们的网站,每个人都使用相同版本的 Firefox”。但是 5 年或 10 年后呢?
【讨论】:
【参考方案11】:另请参阅下面的“层之间的数据传播”部分,我认为它提出了令人信服的论点:
http://galaxy.andromda.org/docs/andromda-documentation/andromda-getting-started-java/java/index.html
【讨论】:
【参考方案12】:在使用视图时,借助“Value Injecter”等工具和表示层中的“映射器”概念,可以更容易地理解每段代码。如果您有一点代码,您不会立即看到优势,但是当您的项目越来越大时,您会很高兴在使用视图时不必进入服务的逻辑,存储库以了解视图模型。 View Model 是浩瀚反腐层中的又一守护者,在长期项目中价值连城。
我认为使用视图模型没有优势的唯一原因是,如果您的项目足够小且足够简单,可以将视图直接绑定到模型的每个属性。但是如果在未来,需求变化和视图中的一些控件将不会绑定到模型并且您没有视图模型概念,您将开始在许多地方添加补丁,并且您将开始拥有遗留代码你不会欣赏的。当然,您可以进行一些重构以在 view-viewmodel 中转换您的视图模型并遵循 YAGNI 原则,如果您不需要它,则不要添加代码,但对于我自己来说,这是我必须遵循的最佳实践来添加表示层仅公开视图模型对象。
【讨论】:
【参考方案13】:这是一个真实的例子,说明为什么我认为将域实体与视图分开是一种很好的做法。
几个月前,我创建了一个简单的 UI,通过一系列 3 个仪表显示土壤样本中氮、磷和钾的值。每个仪表都有红色、绿色和红色部分,也就是说,每个组件可能太少或太多,但中间有一个安全的绿色水平。
没有多想,我为我的业务逻辑建模以提供这 3 种化学成分的数据和一个单独的数据表,其中包含有关 3 种情况中每一种情况下可接受水平的数据(包括正在使用的测量单位,即摩尔或百分比)。然后我为我的 UI 建模以使用一个非常不同的模型,这个模型关注的是仪表标签、值、边界值和颜色。
这意味着当我后来必须显示 12 个组件时,我只是将额外的数据映射到 12 个新的仪表视图模型中,它们就会出现在屏幕上。这也意味着我可以轻松地重用仪表控件并让它们显示其他数据集。
如果我将这些仪表直接耦合到我的域实体中,我将没有上述任何灵活性,并且将来的任何修改都会令人头疼。在 UI 中建模日历时,我遇到了非常相似的问题。如果有 10 人以上的与会者要求日历约会变为红色,则处理此问题的业务逻辑应保留在业务层中,并且 UI 中的所有日历都需要知道,是否已指示它变红,它应该不需要知道为什么。
【讨论】:
【参考方案14】:在通用语义和特定领域语义之间添加额外映射的唯一合理原因是,您拥有(访问)现有代码(和工具)主体,这些代码基于不同于您的领域语义的通用(但可映射)语义.
领域驱动设计在与一组正交的功能领域框架(例如 ORM、GUI、工作流等)结合使用时效果最佳。永远记住,只有在外层邻接中,域语义才需要暴露。通常这是前端 (GUI) 和持久后端 (RDBM,ORM)。任何有效设计的中间层都可以而且应该是域不变的。
【讨论】:
第 1 段:不要创建不必要的抽象(例如可重用组件),除非您实际上会在不同的应用程序之间共享它们。第 2 段:我想知道通用 GUI 如何在这么多不同的域中工作。备注:这个行业太破烂了,都不好笑了……以上是关于为啥我应该将我的域实体与我的表示层隔离?的主要内容,如果未能解决你的问题,请参考以下文章