通过服务公开 JPA 实体

Exposing JPA entities through Service

我们正在使用 AngularJS,Rest,JPA 开发 Web 应用程序。我已经阅读了一些关于域实体不应通过服务公开的文章。 我知道这是紧密耦合,可能存在循环引用、关注点分离,这对我来说似乎是有效的。 但后来我看到有关将 jpa 和 jaxb 映射应用于同一模型的文章,eclipseLink moxy 就是一个例子。

然后是Spring数据REST,它通过rest暴露jpa实体api。(可能Spring数据REST用于解决手头的不同问题)

所以我有点困惑。回答以下 2 个问题和场景,其中一个比另一个更好。

  1. 将 jaxb 和 JPA 注释应用到同一个领域模型有什么好处?这样做主要是为了避免中间出现 DTO 层数?

  2. 仅当您正在开发需要公开 CRUD 操作并且确实不涉及其他业务功能的应用程序时,才应该使用 Spring 数据 REST 吗?

使用 DTO 需要一定的开销:必须创建 DTO classes;实体 classes 必须在序列化之前映射到 DTO。

DTO 也 "feel wrong" 因为它们似乎违反了 "don't repeat yourself" 原则。查看一个实体和一个 DTO 并发现它们本质上相同是令人沮丧的。

因此,直接公开您的 JPA 实体可以减少代码并减少使用代码时的精神开销。您的控制器、服务和存储库都处理相同的 classes.

但是存在问题:

  • 您的内部表示与您要向 API 用户公开的界面不匹配。

    真实示例:层次文档,其中根文档是 article,子文档是 sections。在内部,每个文档都与可能为空的父文档有关系。

    直接通过 REST 接口公开此实体需要客户端将 id(或 HATEOAS 中的 url)作为其提交实体的一部分正确地 link 一起发送文件。

    更好的方法是在创建小节时让 api 用户 POST 到 /articles/{id}/sections/

  • 公开内部数据。

    您的实体可能包括标识符、磁盘上的路径等,它们并非真正供 public 使用。在序列化时你必须 @JsonIgnore 这些。反序列化时,您还需要手动从实体中填充这些。

  • 性能。

    同上一点;也许您的实体包含一个大数据对象。您希望用户能够 POST 或 PUT 这些,但不应要求他们下载对象只是为了阅读实体的其他字段。

  • 潜在的序列化问题。

    延迟加载的集合是这里的 classic 失败案例。

  • 比必要的更复杂的序列化表示。

    现实世界的例子:一个 word 实体与 synonyms 的集合;同义词取决于单词。直接序列化实体将导致:

    { "id":12, 
      "word": "cat",
      "synonyms": [
        { "id": 23,
          "synonym": "kitty"
        },
        { "id": 34,
          "synonym": "pussycat"
        }
      ]
    }
    

    更好的模型是:

    { "word": "cat",
      "synonyms": [
        "kitty",
        "pussycat"
      ]
    }
    
  • 同一个 class 上有很多注释,这会降低清晰度。

    根据您的实体复杂性,您可能有 JPA 注释(@Entity@Id@GeneratedValue@Column@ManyToMany@MappedBy, 等), XML 注释(@XmlRootElement, @XmlType, @XmlElement, 等), JSON 注释(@JsonInclude, @JsonIgnore@JsonProperty、等)和验证注释(@NotNull@Null、等)都在同一个 class.

由于所有这些问题,我通常使用 DTO。我手动将实体映射到 DTO,但有些项目试图让这更容易,比如 Dozer.

我可以想象使用spring-data-rest或直接公开我的实体只有在我有非常简单的实体,它们之间没有太多关系,并且实体模型和实体模型之间有很高的对应关系的情况下我希望向用户公开的模型。