元数据

本节详细介绍了基于 Spring Data REST 的应用所提供的各种元数据形式。

应用级配置文件语义 (ALPS)

ALPS 是一种数据格式,用于定义应用级语义的简单描述,其复杂性与 HTML microformats 类似。ALPS 文档可用作配置文件,以解释使用应用无关的媒体类型(如 HTML、HAL、Collection+JSON、Siren 等)的文档的应用语义。这提高了配置文件文档在不同媒体类型之间的可重用性。
— M. Admundsen / L. Richardson / M. Foster
https://tools.ietf.org/html/draft-amundsen-richardson-foster-alps-00

Spring Data REST 为每个导出的仓库提供一个 ALPS 文档。它包含关于 RESTful 转换以及每个仓库属性的信息。

Spring Data REST 应用的根目录有一个 profile 链接。假设你有一个包含 persons 和相关 addresses 的应用,根文档如下所示

{
  "_links" : {
    "persons" : {
      "href" : "http://localhost:8080/persons"
    },
    "addresses" : {
      "href" : "http://localhost:8080/addresses"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile"
    }
  }
}

profile 链接,如 RFC 6906 中定义的那样,是包含应用级详细信息的地方。ALPS 草案规范 旨在定义一种特定的配置文件格式,我们将在本节后面进行探讨。

如果你导航到 localhost:8080/profile 的 profile 链接,你会看到类似以下内容

{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/profile"
    },
    "persons" : {
      "href" : "http://localhost:8080/profile/persons"
    },
    "addresses" : {
      "href" : "http://localhost:8080/profile/addresses"
    }
  }
}
在根级别,profile 是一个单一链接,无法提供多个应用配置文件。这就是为什么你必须导航到 /profile 来查找每个资源的元数据的链接。

如果你导航到 /profile/persons 并查看 Person 资源的 profile 数据,你会看到类似以下示例的内容

{
  "version" : "1.0",
  "descriptors" : [ {
    "id" : "person-representation", (1)
    "descriptors" : [ {
      "name" : "firstName",
      "type" : "SEMANTIC"
    }, {
      "name" : "lastName",
      "type" : "SEMANTIC"
    }, {
      "name" : "id",
      "type" : "SEMANTIC"
    }, {
      "name" : "address",
      "type" : "SAFE",
      "rt" : "http://localhost:8080/profile/addresses#address"
    } ]
  }, {
    "id" : "create-persons", (2)
    "name" : "persons", (3)
    "type" : "UNSAFE", (4)
    "rt" : "#person-representation" (5)
  }, {
    "id" : "get-persons",
    "name" : "persons",
    "type" : "SAFE",
    "rt" : "#person-representation"
  }, {
    "id" : "delete-person",
    "name" : "person",
    "type" : "IDEMPOTENT",
    "rt" : "#person-representation"
  }, {
    "id" : "patch-person",
    "name" : "person",
    "type" : "UNSAFE",
    "rt" : "#person-representation"
  }, {
    "id" : "update-person",
    "name" : "person",
    "type" : "IDEMPOTENT",
    "rt" : "#person-representation"
  }, {
    "id" : "get-person",
    "name" : "person",
    "type" : "SAFE",
    "rt" : "#person-representation"
  } ]
}
1 Person 资源的属性详细列表,标识为 #person-representation,列出了属性的名称。
2 支持的操作。这个操作表明如何创建一个新的 Person
3 namepersons,这表明(因为它采用复数形式)POST 操作应应用于整个集合,而不是单个 person
4 typeUNSAFE,因为此操作会改变系统的状态。
5 rt#person-representation,这表明返回的资源类型将是 Person 资源。
此 JSON 文档的媒体类型是 application/alps+json。这与之前的 JSON 文档不同,之前的文档媒体类型是 application/hal+json。这些格式不同,并受不同规范的管理。

当你查看一个集合资源时,你还可以在 _links 集合中找到一个 profile 链接,如下例所示

{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/persons" (1)
    },
    ... other links ...
    "profile" : {
      "href" : "http://localhost:8080/profile/persons" (2)
    }
  },
  ...
}
1 此 HAL 文档表示 Person 集合。
2 它有一个指向相同 URI 的 profile 链接,用于元数据。

同样,默认情况下,profile 链接提供 ALPS。但是,如果你使用 Accept,它可以提供 application/alps+json

超媒体控制类型

ALPS 为每个超媒体控制显示类型。它们包括

表 1. ALPS 类型
类型 描述

SEMANTIC (语义)

一个状态元素(例如 HTML.SPANHTML.INPUT 等)。

SAFE (安全)

触发安全、幂等状态转换的超媒体控制(例如 GETHEAD)。

IDEMPOTENT (幂等)

触发不安全、幂等状态转换的超媒体控制(例如 PUTDELETE)。

UNSAFE (不安全)

触发不安全、非幂等状态转换的超媒体控制(例如 POST)。

在前面所示的表示部分,来自应用的数据片段被标记为 SEMANTICaddress 字段是一个链接,涉及安全的 GET 操作来检索。因此,它被标记为 SAFE。超媒体操作本身映射到前面表格中所示的类型。

带投影的 ALPS

如果你定义了任何投影,它们也会列在 ALPS 元数据中。假设我们也定义了 inlineAddressnoAddresses,它们会出现在相关的操作中。(有关这两个投影的定义和讨论,请参见“投影”。)也就是说,GET 会出现在整个集合的操作中,而 GET 会出现在单个资源的操作中。以下示例显示了 get-persons 子部分的替代版本

...
  {
    "id" : "get-persons",
    "name" : "persons",
    "type" : "SAFE",
    "rt" : "#person-representation",
    "descriptors" : [ { (1)
      "name" : "projection",
      "doc" : {
        "value" : "The projection that shall be applied when rendering the response. Acceptable values available in nested descriptors.",
        "format" : "TEXT"
      },
      "type" : "SEMANTIC",
      "descriptors" : [ {
        "name" : "inlineAddress", (2)
        "type" : "SEMANTIC",
        "descriptors" : [ {
          "name" : "address",
          "type" : "SEMANTIC"
        }, {
          "name" : "firstName",
          "type" : "SEMANTIC"
        }, {
          "name" : "lastName",
          "type" : "SEMANTIC"
        } ]
      }, {
        "name" : "noAddresses", (3)
        "type" : "SEMANTIC",
        "descriptors" : [ {
          "name" : "firstName",
          "type" : "SEMANTIC"
        }, {
          "name" : "lastName",
          "type" : "SEMANTIC"
        } ]
      } ]
    } ]
  }
...
1 出现了一个新属性 descriptors,它包含一个数组,其中有一个条目 projection
2 projection.descriptors 内部,我们可以看到 inLineAddress。它渲染 addressfirstNamelastName。在投影中渲染关系会导致包含内联的数据字段。
3 noAddresses 提供了一个包含 firstNamelastName 的子集。

有了所有这些信息,客户端不仅可以推断出可用的 RESTful 转换,还可以在一定程度上推断出与后端交互所需的数据元素。

向 ALPS 描述添加自定义详细信息

你可以创建出现在 ALPS 元数据中的自定义消息。为此,请创建 rest-messages.properties 文件,如下所示

rest.description.person=A collection of people
rest.description.person.id=primary key used internally to store a person (not for RESTful usage)
rest.description.person.firstName=Person's first name
rest.description.person.lastName=Person's last name
rest.description.person.address=Person's address

这些 rest.description.* 属性定义了要为 Person 资源显示的详细信息。它们会改变 person-representation 的 ALPS 格式,如下所示

...
  {
    "id" : "person-representation",
    "doc" : {
      "value" : "A collection of people", (1)
      "format" : "TEXT"
    },
    "descriptors" : [ {
      "name" : "firstName",
      "doc" : {
        "value" : "Person's first name", (2)
        "format" : "TEXT"
      },
      "type" : "SEMANTIC"
    }, {
      "name" : "lastName",
      "doc" : {
        "value" : "Person's last name", (3)
        "format" : "TEXT"
      },
      "type" : "SEMANTIC"
    }, {
      "name" : "id",
      "doc" : {
        "value" : "primary key used internally to store a person (not for RESTful usage)", (4)
        "format" : "TEXT"
      },
      "type" : "SEMANTIC"
    }, {
      "name" : "address",
      "doc" : {
        "value" : "Person's address", (5)
        "format" : "TEXT"
      },
      "type" : "SAFE",
      "rt" : "http://localhost:8080/profile/addresses#address"
    } ]
  }
...
1 rest.description.person 的值映射到整个表示。
2 rest.description.person.firstName 的值映射到 firstName 属性。
3 rest.description.person.lastName 的值映射到 lastName 属性。
4 rest.description.person.id 的值映射到 id 属性,该字段通常不显示。
5 rest.description.person.address 的值映射到 address 属性。

提供这些属性设置会导致每个字段都有一个额外的 doc 属性。

Spring MVC(它是 Spring Data REST 应用的核心)支持 locale,这意味着你可以捆绑包含不同消息的多个属性文件。

JSON Schema

JSON Schema 是 Spring Data REST 支持的另一种元数据形式。根据其网站所述,JSON Schema 具有以下优点

  • 描述你现有数据格式

  • 清晰、人类可读和机器可读的文档

  • 完整的结构验证,对于自动化测试和验证客户端提交的数据很有用

上一节所示,你可以通过从根 URI 导航到 profile 链接来获取此数据。

{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/profile"
    },
    "persons" : {
      "href" : "http://localhost:8080/profile/persons"
    },
    "addresses" : {
      "href" : "http://localhost:8080/profile/addresses"
    }
  }
}

这些链接与前面所示的相同。要检索 JSON Schema,你可以使用以下 Accept 头调用它们:application/schema+json

在这种情况下,如果你运行 curl -H 'Accept:application/schema+json' localhost:8080/profile/persons,你会看到类似以下内容的输出

{
  "title" : "org.springframework.data.rest.webmvc.jpa.Person", (1)
  "properties" : { (2)
    "firstName" : {
      "readOnly" : false,
      "type" : "string"
    },
    "lastName" : {
      "readOnly" : false,
      "type" : "string"
    },
    "siblings" : {
      "readOnly" : false,
      "type" : "string",
      "format" : "uri"
    },
    "created" : {
      "readOnly" : false,
      "type" : "string",
      "format" : "date-time"
    },
    "father" : {
      "readOnly" : false,
      "type" : "string",
      "format" : "uri"
    },
    "weight" : {
      "readOnly" : false,
      "type" : "integer"
    },
    "height" : {
      "readOnly" : false,
      "type" : "integer"
    }
  },
  "descriptors" : { },
  "type" : "object",
  "$schema" : "https://json-schema.fullstack.org.cn/draft-04/schema#"
}
1 导出的类型
2 属性列表

如果你的资源链接到其他资源,会有更多详细信息。

当你查看一个集合资源时,你还可以在 _links 集合中找到一个 profile 链接,如下例所示

{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/persons" (1)
    },
    ... other links ...
    "profile" : {
      "href" : "http://localhost:8080/profile/persons" (2)
    }
  },
  ...
}
1 此 HAL 文档表示 Person 集合。
2 它有一个指向相同 URI 的 profile 链接,用于元数据。

同样,默认情况下,profile 链接提供 ALPS。如果你向其提供 application/schema+jsonAccept,它会渲染 JSON Schema 表示。