数据传输对象是一个在进程之间携带数据的对象。使用它的动机是,进程之间的通信通常是借助于远程接口来完成的,其中每一次调用都是一个昂贵的操作。因为每次调用的大部分成本都与客户端和服务器之间的往返时间有关,所以减少调用次数的一种方法是使用一个对象(DTO),该对象聚合了本应由多次调用传输的数据,但只由一次调用来提供。
-维基百科
我相信(现在也是如此),它应该是过去的事情了。然而,它的使用似乎仍然很普遍。
我不否认有一些有效的理由来转换数据。然而,除了传统的DTO过程,还有其他的选择。
-
从服务层返回一个业务对象
在我以前做过的项目中,我们直接将BO映射到从数据库中读取的实体。 -
将BO转化为表现层中的DTO
-
从表现层返回DTO
返回实体本身
当实体的属性是需要显示的属性的超集时,就不需要聚合额外的属性了。将实体转换为DTO不仅是多余的。它阻碍了性能。
在这种情况下,最好的方法是返回实体本身。
JPA投射
我们在一个特定的上下文中对特定的数据进行请求。因此,当调用到达数据访问层时,所需数据的范围是完全已知的:执行一个适合这个范围的SQL查询是有意义的。
为此,JPA提供了投影。从本质上讲,查询中的投影允许精确选择一个人想要的数据。下面是一个例子;给定一个Person 实体类和一个PersonDetails 常规类。
CriteriaQuery<PersonDetails> q = cb.createQuery(PersonDetails.class);
Root<Person> c = q.from(Person.class);
q.select(cb.construct(PersonDetails.class,
c.get(Person_.firstName),
c.get(Person_.lastName),
c.get(Person_.birthdate)
));
杰克逊转换器
具体到JSON,我们可以将提供正确数据的过程委托给序列化框架,例如 Jackson。它背后的想法是:主代码像往常一样处理实体,在边缘,一个Jackson转换器将其转换为所需的JSON结构。
如果需要的数据较少,那是小事一桩。如果更多,那么转换器就需要额外的依赖关系来获得数据的位置。当然,如果这些数据来自同一个数据存储,这不是很好,而上面的替代方案更有意义。如果不是,这也是一种选择。
GraphQL
最后但并非最不重要的是,我们可以返回完整的实体,让客户端决定哪些数据在其上下文中是有意义的。
GraphQL就是围绕这个想法建立的。Facebook创建了它,现在它是完全开源的。它的主要优势是提供了一个规范,并在此基础上提供了很多特定语言的实现。
为你的API提供查询语言
GraphQL是一种用于API的查询语言,也是用你现有的数据来完成这些查询的运行时间。GraphQL为你的API中的数据提供了完整的、可理解的描述,使客户有能力准确地要求他们所需要的东西,而不是更多,使API更容易随着时间的推移而发展,并启用强大的开发者工具。
结论
当业务模型和表现模型之间存在差距时,我们很容易回到古老的 "模式",比如DTO。然而,上面的任何一个替代方案都可能更有意义。