我目前正在为一个项目创建REST-API,并且一直在阅读关于最佳实践的文章。许多人似乎反对dto,只是简单地公开域模型,而其他人似乎认为dto(或用户模型或任何您想要称呼它的东西)是不好的实践。就我个人而言,我认为这篇文章很有意义。

但是,我也理解dto的缺点,包括所有额外的映射代码、可能与dto对应物100%相同的领域模型等等。

我们的API主要是为了让其他客户端可以使用数据而创建的,但是如果我们做得对,我们也想在可能的情况下将其用于我们自己的web GUI。

The thing is that we might not want to expose all the domain data to the other client users. Much of the data will only make sense in our own web application. Also, we might not want to expose all data about an object in all scenarios, especially relationships to other objects and so on. For example, if we expose a list of a particular object we would not necessarily want to expose the entire object hierarchy; so that the object's children will not be exposed, but can be discovered through links (hateoas).

我该如何着手解决这个问题呢?我在考虑在我们的领域模型上使用Jackson mixins来控制在不同的场景下会暴露哪些数据。或者我们应该一直使用dto——即使考虑到它的缺点和争议?


正如你已经说过的,这显然是一个与意见有关的问题。我本人更倾向于no - dto方法,仅仅是因为您需要的所有样板代码。

这主要适用于json/rest api的响应端。我甚至写了一个jackson插件来避免为这些情况写很多json视图/过滤器:https://github.com/Antibrumm/jackson-antpathfilter

另一方面,dto在这类api的请求输入端是一件好事。例如,考虑到双向关系,直接处理实体是相当困难的。另外,你也不想让调用者修改一个“creator”属性。因此,在映射此类请求期间,您需要禁用某些字段。


如果API是公共的,并且必须支持多个版本,那么就必须使用dto。

另一方面,如果它是私有API,并且您同时控制客户端和服务器,我倾向于跳过dto,直接公开域模型。


我倾向于使用dto。

我不喜欢它的缺点,但其他选择似乎更糟糕:

公开域对象可能会导致安全问题和数据泄露。 Jackson注释似乎可以解决问题,但它很容易犯错误并暴露不应该暴露的数据。 在设计DTO类时,犯这样的错误要难得多。

另一方面,DTO方法的缺点可以通过对象到对象映射和Lombok来减少样板文件。


为什么应该在REST API中使用dto

DTO代表数据传输对象。

创建此模式的目的非常明确:将数据传输到远程接口,就像web服务一样。这种模式非常适合REST API,从长远来看dto将为您提供更大的灵活性。

表示应用程序领域的模型和表示API处理的数据的模型是(或者至少应该是)不同的关注点,并且应该彼此分离。当从应用程序域模型中添加、删除或重命名字段时,您不希望破坏API客户机。

当你的服务层在域/持久化模型上操作时,你的API控制器应该在一组不同的模型上操作。例如,当您的域/持久化模型发展到支持新的业务需求时,您可能希望创建API模型的新版本来支持这些更改。当新版本发布时,您还可能希望弃用旧版本的API。当它们解耦时,这是完全有可能实现的。


只是提一下公开dto而不是持久性模型的一些好处:

Decouple persistence models from API models. DTOs can be tailored to your needs and they are great when exposing only a set of attributes of your persistence entities. You won't need annotations such as @XmlTransient and @JsonIgnore to avoid the serialization of some attributes. By using DTOs, you will avoid a hell of annotations in your persistence entities, that is, your persistence entities won't be bloated with non persistence related annotations. You will have full control over the attributes you are receiving when creating or updating a resource. If you are using Swagger, you can use @ApiModel and @ApiModelProperty annotations to document your API models without messing your persistence entities. You can have different DTOs for each version of your API. You'll have more flexibility when mapping relationships. You can have different DTOs for different media types. Your DTOs can have a list of links for HATEOAS. That's the kind of thing that shouldn't be added to persistence objects. When using Spring HATEOAS, you can make your DTO classes extend RepresentationModel (formerly known as ResourceSupport) or wrap them with EntityModel (formerly known as Resource<T>).

处理样板代码

您不需要手动将持久性实体映射到dto,反之亦然。您可以使用许多映射框架来完成此任务。例如,看一看MapStruct,它是基于注释的,作为Maven注释处理器工作。它在CDI和基于spring的应用程序中都工作得很好。

您还可以考虑使用Lombok为您生成getter、setter、equals()、hashcode()和toString()方法。


相关:为了给DTO类起更好的名字,请参考这个答案。