【问题标题】:Is it a good practice to create many DTO for a model?为模型创建许多 DTO 是一种好习惯吗?
【发布时间】: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,它就像一个用户管理系统,管理员用户可以看到一个包含用户名和用户电子邮件列表的屏幕,它可以单击一些用户名以打开一个新窗口有关所选用户的更多信息,例如姓名、地址、电话号码、年龄等...你明白了吗?

标签: java hibernate dto serializable


【解决方案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

      【讨论】:

        猜你喜欢
        • 2016-04-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-21
        • 2020-06-04
        • 1970-01-01
        • 1970-01-01
        • 2017-12-09
        • 1970-01-01
        相关资源
        最近更新 更多