这是一个好的做法吗?为一个模型创建许多数据传输对象(DTO)?

huangapple go评论79阅读模式
英文:

Is it a good practice to create many DTO for a model?

问题

假设我有一个拥有许多字段的 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;
}
英文:

Assuming I have a class User with many fields:

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

But I will not always need all User attributes in frontend. Each screen needs only some of User's fields. Is it a good practice to make DTO classes for each screen, since they access different attributes? Like this:

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;
}

答案1

得分: 4

我将只创建一个DTO类,例如,将要从后端获取并设置的字段列表传递给它的构造函数。所有其他字段将为null。

字段列表将由前端传递。

我认为这种方法非常灵活/动态。

它还避免了需要维护多个类。

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

英文:

I would create just one DTO class but e.g. pass to its constructor
the list of the fields which I want to be pulled and set by the backend.
All other fields will be null.

The list of fields will be passed in by the front-end.

I find this approach pretty flexible/dynamic.

It also avoids multiple classes to maintain.

I don't know if this approach matches any best practices or enterprise patterns
but creating multiple DTO classes definitely sounds worse.

答案2

得分: 3

只使用一个DTO或直接使用实体往往会带来较高的成本,通常这个成本只有在后期才会显现出来,因此我建议任何人都像您在这里所做的一样,为每个用例创建一个DTO。

以下是一些原因/成本:

  • 如果API的用户看到了访问未加载的状态的访问器,这将触发延迟加载,这要么会导致性能下降,要么会触发延迟初始化异常。如果通过会话传递对象,则可能会丢失对象生成的上下文,因此您可能无法始终确定加载了哪个状态。
  • 每次加载所有数据可能效率低下。如果有些文本列包含大量数据,这些数据必须通过网络传输,并且作为Java对象实体化。如果您不使用这些数据,将其加载完全是没有意义的。有人可能会说这是微不足道的,但这取决于您的用例。最糟糕的情况是什么?数据库管理系统执行全表扫描或效率较低的索引扫描,而不是仅对索引进行扫描,因为您指示数据库管理系统加载列的值。
  • 并不是所有要为客户端提供的状态都应该在关系表示中。如果进行聚合或使用表达式来连接列,您需要一个DTO。

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

我创建了这个库,以便在JPA模型和自定义接口或抽象类定义的模型之间实现轻松映射,类似于功能强化版的Spring Data Projections。想法是您可以根据自己的喜好定义目标结构(领域模型),并通过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

英文:

Using "just" one DTO or the entity directly comes with a high cost that you usually only pay later, so I would recommend anyone to create a DTO per use case just like you did here.

Here are some of the reasons/costs for this:

  • If the user of the API sees accessors for state that isn't loaded this will trigger lazy loading which will either result in bad performance or lazy initialization exceptions. If objects are passed through a session, you might lose the context how the object came to be, so you might not always be able to tell which state is loaded.
  • It might be inefficient to load all data all the time. If you have some text columns that contain lots of data this has to be transferred over the wire and materialized as Java Objects etc. If you don't use the data, it's just pointless to load it at all. One might say this is negligible, but it depends on your use case. The worst that can happen? The DBMS does a full table scan or a less efficient index scan rather than index only scan because you instruct the DBMS to load the value for a column.
  • Not all the state that you want to provide for a client should be in the relational representation. If you do aggregations or use expressions for e.g. concatenating columns together, you need a DTO.

Having said that, this is a perfect use case for Blaze-Persistence Entity Views.

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:

@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();
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

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

The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

答案3

得分: 2

因为OP表示系统在相同的前端和相同的上下文中使用,我认为这是不良实践,不建议使用不同的DTO。

原因:

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

此外,更多的类通常意味着更高的复杂性。因此,我建议保持类的数量合理较少。如果不想使用ETag和/或前端不保留服务器发送的实体的存储,我建议采用彼得·彼得罗夫的答案中描述的方法


只有在上下文发生变化时,表示形式才应更改。例如,如果表示在用户前端和管理员前端之间差异很大,则可能需要不同的DTO。

NicolasComment:https://stackoverflow.com/questions/63588494-is-it-a-good-practice-to-create-many-dto-for-a-model?noredirect=1#comment112445409_63588494
WikiEtag:https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation
AnswerPeterPetrov:https://stackoverflow.com/a/63588778/4216641

英文:

Since OP said that the system is used from the same frontend and in the same context, I would consider it bad practice and not recommend using different DTOs.

Reasoning:

Modern frontends normally manage a store of all entites received by the backend. So the frontend can look up entities in the store and - depending on caching policies - load them from the store rather than requesting them from the server. Thus, instead of fetching users part-by-part, the user is transmitted once. This can be further improved by using ETags. While the usage of Etags will hardly improve latency, it can improve network load since the response to a matching ETag is a 304/Not Modified without a body (!) rather than a 200/OK with body. While ETags can be used with many Dto-Objects, more (partial) updates might occur. If, for example, the email and the phone number of a user changes, and the frontend first requests the UserToScreenADTO, it would get a response body that - among other things - contains the new email. When it then later requests a UserToScreenBDTO, it would - again - receive a response body containing the new phone number. With only one DTO, the frontend will receive one updated representation on the first request, and all successive request (made with the matching ETag) will result in a 304/Not Modified.

Furthermore, more classes normally mean higher complexity. Thus, I would recommend to keep the number of classes reasonably small. If using an ETag is not wanted and/or the frontend does not keep a store of server-sent entities, I would recommend the approach described in peter petrov's answer.


The representation should only change if the context changes. If the representations differ drastically between, for example, a user-frontend and an admin-frontend, then different DTOs may be justified.

huangapple
  • 本文由 发表于 2020年8月26日 07:33:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/63588494.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定