视图层中的 DTO 或域模型对象?
Posted
技术标签:
【中文标题】视图层中的 DTO 或域模型对象?【英文标题】:DTO or Domain Model Object in the View Layer? 【发布时间】:2011-02-10 10:12:50 【问题描述】:我知道这可能是一个古老的问题,但更好的做法是什么?在应用程序的所有层中使用域模型对象,甚至在 JSP 上直接将值绑定到它们(我使用的是 JSF)。或者在 DAO 或 Service 层将领域模型对象转换为 DTO,并将轻量级 DTO 发送到表示层。
有人告诉我,使用 DTO 没有任何意义,因为对数据库的更改将导致对所有 DTO 的更改,而在任何地方使用模型对象只需要对受影响的模型对象进行更改。然而,DTO 的易用性和轻量级特性似乎胜过这一点。
我应该注意我的应用程序使用 Hibernate 模型对象并使用它自己的自定义创建的模型对象(意味着不绑定到任何数据库会话,始终分离)。上述任何一种情况对严格的模型对象模式更有利吗?就延迟初始化异常之类的问题而言,使用 Hibernate 是一个巨大的 PITA。
我正在编辑这个问题,希望进一步讨论(不确定我这样做是否正确):
我对模型对象的问题是它们根本不灵活。下面的评论说应用程序的设计应该使模型对象可以在所有层中使用。为什么?如果用户想要一个荒谬的功能,我是否应该告诉他们,'这不适用于模型对象'?
简单明了,有时模型对象不起作用。你可能有:
public class Teacher
List<Student> students;
[tons of other Teacher-related fields]
public class Student
double gpa;
[tons of other Student-related fields]
但也许您不需要所有这些信息。您只需要老师的姓氏、他们今年教的学生人数以及所有学生的平均 GPA 总和。在那种情况下你会怎么做?检索完整的教师信息和学生关系,然后您的代码计算学生列表,然后计算其中所有 gpa 的总平均值?这似乎比简单地使用“String lastName”、“int numStudents”和“double combineGpa”创建一个 DTO 更加努力;
听起来我似乎已经下定决心,但我还没有在一个应用程序中工作,其中模型对象可以在每个实例中完全干净地使用。具有非同寻常的用户需求的常规实际应用程序无法以这种方式工作。
【问题讨论】:
【参考方案1】:使用 DTO 的原因之一是当您需要向不同类型的用户显示信息时。例如。你有一个“帐户”域模型,它有一些属性,例如“created_at”。根据您的业务逻辑,您可以向管理员和用户显示帐户,但实际上不允许用户知道帐户的创建日期(出于某些公司机密原因)。为管理员和用户提供域模型可能非常危险,特别是如果您使用 JSON API,但如果您将表示拆分为 2 个不同的 DTO - 对于管理员和用户,那么它会更安全。不得不说,处理和维护这件事需要更多的时间,但如果你的应用需要这种类型的安全性和严格性,那么你别无选择。
【讨论】:
【参考方案2】:类的行为或其内部方法不应暴露给与其行为无关的层。传输数据,而不是行为。使用域内的域对象。 Web 不是受控域,UI 开发人员无需关心域行为,只需关心数据。
域必须被封装,并防止被不关心域健康的人修改。
泄露行为不是最好的习惯。
如果它是一个小项目,那么也要使用正确的原则来构建它。这样,我们始终牢记我们为什么做我们所做的事情,而不仅仅是如何做。
【讨论】:
【参考方案3】:我认为我们首先应该考虑的是引入新层的成本。以 DTO 为例 - 这样做我们需要一个映射。正如有人所说,翻译是邪恶的,应该尽可能避免。
另一方面,我认为您通常不应该做的事情很少。那些说所有 DTO 都是邪恶的人是错误的——这总是取决于用例!他们真的有道理吗?
最后,我个人认为应该让域对象进入视图本身。想象一下检票口集成是什么样的。但以 Spring MVC 为例 - 域可能会留在应用层中......
【讨论】:
【参考方案4】:在我看来,在每一层都使用域模型对象完全没有问题。你说你不需要所有的信息。当您在 JSP 中时,只使用您需要的数据。没有人强迫您获取所有财产。您还说过,您需要进行与对象属性相关的计算,以获得 GPA、学生人数等。您有 3 个选项:在您的域模型对象中创建合成属性,为您返回正确的数据,结束得很好整洁;在控制器或服务层中进行计算,并通过适当的 getter 公开它们;或者在你的 JSP 中处理它。无论如何,您都需要检索/编译/整理数据,所以为什么要使用 DTO 增加更多复杂性。
此外,对于每个 DTO,您正在创建 a.) 一个您现在必须维护的额外类,以及 b.) 在构建和填充 DTO 的某个类中的某个位置至少有 1 个额外方法(DAO、工厂方法、等等。)。更多维护 = 6 个月后开发人员不满意。
所以,我反对 DTO。我确实使用它们,但仅在某些情况下使用,例如当我真的需要优化速度和/或内存使用时,并且水合完整域模型对象的成本太高了。 Web 服务是我喜欢使用 DTO 的一个很好的例子。
【讨论】:
【参考方案5】:我认为拥有 DTO 通常不是一种反模式。有很多人和系统在使用它们,您获得的好处是可以独立于域模型进行设计和模块化的解耦视图层。尽管我同意您应该尽可能使用域对象,但是当您将视图层直接绑定到域模型时,您可能会遇到问题。
我在仅包裹领域对象并将大部分操作委托给它们的视图模型方面取得了很好的经验。这将视图和领域层解耦,允许灵活组合领域对象,但实现起来仍然没有太多工作因为 IDE 支持委托模式。
【讨论】:
【参考方案6】:这实际上取决于您的应用程序的复杂性。将领域对象混合到视图层有两个可能的含义:
-
您会很想修改域对象以适应视图层中所需的内容
您的视图层将包含额外的复杂性,这是由于您的域对象提供的内容与您的视图真正需要的内容不匹配而导致的。您可能无法解决这种复杂性,但它可能不属于 View 层。
如果您的域对象很简单并且您的视图很少,那么跳过 DTO 可能是最简单的事情。
另一方面,如果您的领域模型可能会演变并变得复杂,并且如果您的视图可能是多种多样的,那么拥有视图特定对象可能是一个好主意。在 MVC 世界中,使用 ViewModel 很常见,对我来说很有意义。
【讨论】:
【参考方案7】:域对象发送有问题的场景:
-
您可能需要将汇总信息或其他类型的“计算字段”发送到 UI 层(例如 Flex / GWT),并且不想弄乱域对象
您可能会遇到序列化循环对象图的需求(在您的示例中,如果 Student 具有 List 关系),某些协议会遇到问题
在处理框架序列化程序(flex / GWT 序列化程序的 blazeDS)时出现休眠 LazyInitializationException
我不确定在这些情况下这是一个明确的答案
【讨论】:
【参考方案8】:对领域对象的另一票。就领域驱动设计而言,领域模型为王,应尽可能使用。应用程序的设计方式应使大多数层(bar Infrastructure 层)都可以使用域对象。
我认为 DTO 仅在需要序列化对象时才有用。如果没有通过线路传输或进入不兼容的架构,我将不会使用它们。 DTO 模式对于将序列化排除在域对象之外很有用。考虑到 UI/Domain 交互不需要序列化,保持简单并使用实际对象。
【讨论】:
我认为集群环境中的会话状态管理需要序列化?甚至我们现在拥有的模型对象(Hibernate 的和自定义的、分离的)也是可序列化的。 我不认为他指的是您正在谈论的序列化(a-la Serializable)。我认为他的意思是对 XML、JSON、AMF (Flex)、CSV 等内容进行序列化。【参考方案9】:如今,DTO 被广泛认为是一种反模式,建议通常是“不惜一切代价避免它们”。
像 Hibernate 这样的 ORM 框架的主要优点之一是您可以在所有级别上使用域对象,而实际上并不需要 DTO。当然,需要注意的是,您必须花一些时间来思考这些关系:何时使用惰性获取,何时使用渴望,等等。
【讨论】:
是的,我明白了。有时它似乎确实增加了不必要的复杂性。如果我有一个表示教师的对象,并且有时我只需要标题信息(姓名、地址),我必须向前端发送一个填充了一半的域对象。它只是一种误导,对我来说似乎不正确。 @smayers81 - 为什么只在中途填充域对象?这听起来像是一种不必要的优化(当然,如果您已经分析或以其他方式发现了问题)。这听起来像是您问题的根源 - 将您的域对象视为 DTO。为什么不把你的完全水合的领域对象推到前端呢?如果您发现自己有性能问题,那么您就有了 DTO 的案例(或完全绕过自定义对象并直接使用您的框架提供的任何记录集抽象)。 因为我一直觉得,如果不是真的需要,为什么要通过网络发送所有数据。我认为这是 Hibernate 延迟获取想法背后的全部推动力(我可能错了) 我没有看到任何证据支持您关于 DTO 被视为反模式的说法。相反,在设计分层应用程序并遵循 SOLID 原则时,DTO 是从 A 到 B 获取数据的有用工具,而不必担心对象状态。查看 cuttingedge.it/blogs/steven/pivot/entry.php?id=91 和 Steven 的其他帖子,了解一些有用的信息。 在我看来,如果在 90% 的情况下,您在视图中需要的是域实体提供的子集,那么您可以避免使用 DTO(尽管我认为这不是一个好主意长期),但通常,域实体应该是(否则您的域实体太复杂)视图中所需内容的子集,因此您应该在 DTO 上进行组合并保持域与视图分离以上是关于视图层中的 DTO 或域模型对象?的主要内容,如果未能解决你的问题,请参考以下文章
[使用Automapper时,我是否也应该展平/映射视图模型的内部objetc?
简述VO(View Object,视图对象)DTO(Data Transfer Object,数据传输对象)PO(Persistent Object,持久化对象)