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

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

所以我正在寻找一些可以用来支持这一点的具体原因。具体来说:

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

解决方案

很简单,原因是实施和漂移。是的,您的表示层需要了解您的业务对象才能正确表示它们。是的,最初看起来两种类型的对象的实现之间存在很多重叠。问题是,随着时间的推移,双方都会增加一些东西。演示文稿更改以及表示层的需求演变为包括完全独立于业务层(例如颜色)的内容。同时,您的域对象会随着时间的推移而发生变化,如果您没有从界面中进行适当的分离,则可能会通过对业务对象进行看似良性的更改来调整界面层的风险。

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

其他提示

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

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

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

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

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

为什么要将 UI 生成代码放在业务对象中?就我而言,我有单独的对象,它们与 UI 分开生成 UI 代码。我有单独的对象,它们将我的业务对象呈现为 Xml,必须分离层以防止这种类型的污染的想法对我来说是如此陌生,因为为什么要将 HTML 生成代码放入业务对象中......

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

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

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

如果您只保留一个域对象,以便在演示文稿AND域层中使用,那么该对象很快就会变成单一的。它开始包括UI验证代码,UI导航代码和UI生成代码。然后,您很快就会添加所有业务层方法。现在你的业务层和用户界面都混淆了,所有这些都在域实体层搞乱。

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

我不同意。

我认为最好的方法是从表示层中的域对象开始,直到它做出其他方式的意见。

与流行的看法相反,“域对象”与“域对象”相反。和“价值对象”可以愉快地在表示层中共存。这是最好的方法 - 你可以获得两个世界的好处,减少与域对象的重复(和样板代码);以及在请求中使用值对象的定制和概念简化。

我们在服务器和ui上使用相同的模型。而且这很痛苦。我们有一天必须重构它。

问题主要是因为域模型需要被切割成更小的部分才能序列化它而不需要引用整个数据库。这使得在服务器上使用起来更加困难。缺少重要的链接。某些类型也不可序列化,无法发送到客户端。例如'Type'或任何泛型类。它们需要是非泛型的,Type需要作为字符串传输。这会为序列化生成额外的属性,它们是多余的并且令人困惑。

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

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

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


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

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

enter image description here


中等复杂的非 CRUD 应用程序

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

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

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

enter image description here


企业应用复杂

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

enter image description here

这主要是关于依赖性的。组织的核心功能结构有其自身的功能需求,UI应该使人们能够修改和查看核心;但不应要求核心本身适应UI。 (如果需要发生,通常表明核心不是财产设计。)

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

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

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

该死的,我发誓这说坚持。

无论如何,这是同一件事的又一个例子:Parnas的法律规定模块应该保守秘密,秘密是一个可以改变的要求。 (Bob Martin的规则是另一个版本。)在这样的系统中,表示可以独立于进行更改。例如,一家公司以欧元维持价格并在公司办公室使用法语,但希望以普通话的文本用美元来表示价格。 是一样的;演示文稿可以改变。所以,要尽量减少系统的脆弱性—也就是说,为了实现需求的变化必须改变的事物的数量—你把问题分开了。

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

也许您没有广泛地概念化UI层。考虑多种形式的响应(网页,语音响应,印刷字母等)和多种语言(英语,法语等)。

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

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

另见“层间数据传播”一节。在下面,我认为这提出了令人信服的论据:

http:// galaxy .andromda.org /文档/ AndroMDA的的文档/ AndroMDA的-获得起动的Java / JAVA / index.html中

借助“价值注入”等工具以及表示层中“Mappers”的概念在处理视图时,理解每段代码要容易得多。如果您有一些代码,您将不会立即看到优势,但是当您的项目越来越多时,您将非常高兴在处理视图时无需进入服务的逻辑,存储库以了解视图模型。 View Model是广阔反腐败领域的另一个守护者,在长期项目中值得重视黄金。

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

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

几个月前,我创建了一个简单的用户界面,通过一系列3个仪表显示土壤样品中氮,磷和钾的值。每个仪表都有一个红色,绿色和红色部分,即每个部件可能有太少或太多,但中间有一个安全的绿色等级。

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

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

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

在通用语义和域特定语义之间添加附加映射的唯一合理理由是您拥有(访问)基于与域语义不同的通用(但可映射)语义的现有代码体(和工具)。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top