为模型创建许多 DTO 是一种好习惯吗?

Posted

技术标签:

【中文标题】为模型创建许多 DTO 是一种好习惯吗?【英文标题】:Is it a good practice to create many DTO for a model? 【发布时间】:2020-12-14 17:08:47 【问题描述】:

假设我有一个包含许多字段的类 User

public class User 
    public Integer id;
    public String name;
    public String username;
    public Integer age;
    public Address address;
    public String phoneNumber;
    public String email;

但我并不总是需要前端的所有用户属性。每个屏幕只需要一些用户字段。为每个屏幕创建 DTO 类是否是一种好习惯,因为它们访问不同的属性?像这样:

class UserToScreenADTO implements Serializable 
    public String name;
    public String email;


class UserToScreenBDTO implements Serializable 
    public String phoneNumber;
    public Address address;


class UserToScreenCDTO implements Serializable 
    public Integer id;
    public String username;
    public String email;

【问题讨论】:

您能否阐明“我并不总是需要所有字段...”是什么意思?表示是否由同一用户在同一前端使用?还是表示总是被不同的用户或不同的前端使用? 这将是抽象类的一个很好的用例。您需要在模型之间找到您的共同元素并创建一个 BaseUser,您的其他用户将从该基础用户扩展。 嗨,@Turing85。例如,Profile Screen 将访问姓名、电子邮件、电话号码和地址。但是,用户列表屏幕只能访问要在表格中显示的名称和用户名。在同一个前端,我可以有多个屏幕以不同的方式显示用户对象。 @shinjw 并使用给定的代码,您建议将哪些属性放入抽象类? @Turing85,它就像一个用户管理系统,管理员用户可以看到一个包含用户名和用户电子邮件列表的屏幕,它可以单击一些用户名以打开一个新窗口有关所选用户的更多信息,例如姓名、地址、电话号码、年龄等...你明白了吗? 【参考方案1】:

我将只创建一个 DTO 类,但例如传递给它的构造函数 我想由后端拉取和设置的字段列表。 所有其他字段将为空。

字段列表会被前端传入。

我发现这种方法非常灵活/动态。

它还避免了维护多个类。

我不知道这种方法是否符合任何最佳实践或企业模式 但是创建多个 DTO 类肯定听起来更糟。

【讨论】:

【参考方案2】:

由于OP said that the system is used from the same frontend and in the same context,我认为这是不好的做法,不建议使用不同的 DTO。

推理:

现代前端通常管理后端收到的所有实体的存储。因此,前端可以在存储中查找实体,并根据缓存策略从存储中加载它们,而不是从服务器请求它们。因此,用户被传输一次,而不是部分地获取用户。这可以通过使用ETags 进一步改进。虽然使用Etags 几乎不会改善延迟,但它可以改善网络负载,因为对匹配的ETag 的响应是没有正文(!)的304/Not Modified,而不是带有正文的200/OK。虽然ETags 可以与许多 Dto-Object 一起使用,但可能会发生更多(部分)更新。例如,如果用户的电子邮件和电话号码发生变化,并且前端首先请求UserToScreenADTO,它将得到一个包含新电子邮件的响应正文。当它稍后请求UserToScreenBDTO 时,它会再次收到包含新电话号码的响应正文。只有一个 DTO,前端将在第一个请求时收到一个更新的表示,并且所有后续请求(使用匹配的 ETag 发出)将导致 304/Not Modified

此外,更多的类通常意味着更高的复杂性。因此,我建议将类的数量保持在合理的范围内。如果不需要使用ETag 和/或前端没有保存服务器发送实体的存储,我会推荐peter petrov's answer 中描述的方法。


只有当上下文改变时,表示才应该改变。如果表示在用户前端和管理员前端之间存在巨大差异,则可能需要使用不同的 DTO。

【讨论】:

【参考方案3】:

“仅”使用一个 DTO 或直接使用实体会带来高昂的成本,您通常只能在以后支付,因此我建议任何人都像您在此处所做的那样为每个用例创建一个 DTO。

以下是一些原因/成本:

如果 API 的用户看到未加载状态的访问器,这将触发延迟加载,这将导致性能不佳或延迟初始化异常。如果对象是通过会话传递的,您可能会丢失对象是如何产生的上下文,因此您可能无法始终判断加载了哪个状态。 始终加载所有数据可能效率低下。如果您有一些包含大量数据的文本列,则必须通过网络传输并物化为 Java 对象等。如果您不使用数据,则加载它根本没有意义。有人可能会说这可以忽略不计,但这取决于您的用例。可能发生的最坏情况? DBMS 会执行全表扫描或效率较低的索引扫描,而不是仅索引扫描,因为您指示 DBMS 加载列的值。 并非您要为客户端提供的所有状态都应在关系表示中。如果您进行聚合或使用表达式,例如将列连接在一起,您需要一个 DTO。

话虽如此,这是Blaze-Persistence Entity Views 的完美用例。

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:

@EntityView(User.class)
public interface UserToScreenADTO extends Serializable 
    String getName();
    String getEmail();


@EntityView(User.class)
public interface UserToScreenBDTO extends Serializable 
    String getPhoneNumber();
    Address getAddress();


@EntityView(User.class)
public interface UserToScreenCDTO extends Serializable 
    Integer getId();
    String getUsername();
    String getEmail();

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

UserToScreenADTO u = entityViewManager.find(entityManager, UserToScreenADTO.class, id);

Spring Data 集成让您可以像使用 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

【讨论】:

以上是关于为模型创建许多 DTO 是一种好习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章

始终连接 SQL 表是一种好习惯吗?

在将项目部署到服务器之前重置所有迁移是一种好习惯吗?

向 POCO 添加方法或创建单独的类来更新 POCO 的值是一种好习惯吗?

为 useState() 钩子字符串化对象以避免重新渲染是一种好习惯吗

在 Perl 中,哈希键周围的引号是一种好习惯吗?

使用 MongoDB 数据库为每个查询打开一个新连接是一种好习惯吗?