我正在尝试设计一个充分利用超媒体的RESTful服务。
最好,用户代理应该只知道根 URI 以便能够探索服务的所有功能 - 也就是说,我希望它位于成熟度模型中的第 3 级。
现在,用户代理应该能够创建一些资源,并在以后编辑它们。在创建/编辑时,用户代理需要访问其他一些资源/枚举。
FOO资源:
{
"category" : "category chosen from an enumeration of possible categories",
"color" : "color chosen from an enumeration of possible colors",
"aRelatedResource" : "resource identifier from chosen from a collection"
}
鉴于前面提到的要求,我提出了以下模式:
有一个 fooRoot 资源:
{
// no properties, only links
"_links" : {
"foos" : { "href" : "URI-to-foos" },
"fooCreator" : { "href" : "URI-to-plain-fooWriter" }
}
}
在 foo 资源中包含指向 fooWriter的链接:
FOO资源:
{
"category" : "category chosen from an enumeration of possible categories",
"color" : "color chosen from an enumeration of possible colors",
"aRelatedResource" : "resource identifier from chosen from a collection",
"_links" : {
"self" : {...},
"fooEditor" : { "href" : "URI-to-fooWriter-initialized-for-current-foo" }
}
}
fooWriter 如下所示:
{
"fooPayload" : {
"category" : "NULL or pre-initialized",
"color" : "NULL or pre-initialized",
"aRelatedResource" : "NULL or pre-initialized"
},
"_links" : {
"fooPayloadDestination" : { "href" : "URI-to-foos-or-foo" },
"categoryEnum" : { "href" : "URI-to-categories" },
"colorEnum" : { "href" : "URI-to-colors" },
"availableResourcesToRelateWith" : { "href" : "some-other-URI" },
....
.... and even something useful for pre-validation etc.
"validator" : { href : "URI-to-some-resource-or-service" }
}
}
总而言之,任何可以创建和编辑的资源都可能具有关联的编写器资源。
通过获取编写器,用户代理可以非常方便的方式创建/编辑资源。
嵌入在编写器中的有效负载被 POST 发送到其目标,瞧:)
此外,应该有一个根容器,其中包含指向资源及其新资源的编写器的链接(请参阅上面示例中的 fooRoot(。
问题是...
。上面描述的模式是否有众所周知的名称?
...有没有更好的方法来解决创建/编辑问题,在创建/编辑时需要相邻资源并且第三级成熟度仍然"保持"?
一些参考资料:
- 超媒体规模
- 理查森成熟度模型
- 一个REST API很好地利用了超媒体
您描述的内容让我想起了创建和编辑表单链接关系。但是,如果您正在构建一个API,它的使用是相当有限的,因为无论它是如何定义的,您都需要有人对其进行编程。
在我看来,组织上面给出的示例的最简单方法是定义一个根菜单,如下所示:
GET / HTTP/1.1
Accept: application/hal+json
----
HTTP/1.1 200 OK
Content-Type:application/hal+json
{
"_links" : {
"plants" : { "href" : "/plants" }
}
}
plants
关系将保存由给定媒体类型定义的植物资源集合(假设它是application/vnd.biology-example-org.plant
(:
GET /plants HTTP/1.1
Accept: application/hal+json
----
HTTP/1.1 200 OK
Content-Type:application/hal+json
{
"_links" : {
"self" : { "href" : "/plants" },
"plant": [
{
"href" : "/plants/parsnip",
"title" : "The Parsnip",
"type" : "application/vnd.biology-example-org.plant+json"
}
]
}
}
要将新植物添加到与欧洲防风草相关的集合中,请发布到plants
集合资源并通过其链接与 parnsip 相关:
POST /plants HTTP/1.1
Content-Type: application/vnd.biology-example-org.plant+json
{
"title" : "The Carrot - a cousin of the Parsnip",
"category" : "vegetable",
"color" : "orange",
"related" : [ "/plants/parsnip" ]
}
----
HTTP/1.1 201 Created
Location: http://biology.example.org/plants/carrot
要随后修改胡萝卜,请向返回的 URL 发出 PUT:
PUT /plants/carrot HTTP/1.1
Content-Type: application/vnd.biology-example-org.plant+json
{
"title" : "The Carrot - the orange cousin of the Parsnip",
"category" : "vegetable",
"color" : "orange",
"related" : [ "/plants/parsnip" ]
}
----
HTTP/1.1 200 OK
上面的示例使用超文本应用程序语言 (HAL( 使用 JSON 传达"级别 3"REST 语义。HAL很简单,但功能非常强大。我真正喜欢的约定之一是使用关系名称作为 URI,当取消引用时,它直接指向有关该关系的文档以及它可以返回的资源。
如果你想玩这样的实时API,我强烈建议你看看HALtalk,这是HAL的实时演示API。
让我们问一个问题。编辑资源需要什么?
- 对于 HTML,您需要一个 FORM 标签,以及其中的 INPUT 标签。
他们描述了什么?
- 1.(它是一种形式。
- 2.( 它有一个动作 IRI。
- 3.(它有一个方法。
- 4.(它有一个内容类型。
- 5.( 它有多个字段,应由用户填写。也许有验证数据。
在您得到的形式中:
- 6.( 整个表单的标题
- 7.( 输入字段的标签
这与 REST 客户端的需求几乎相同。
- 8.( 它应该包含它已经知道的 REST 客户端的链接关系,因此客户端将使用它来将表单放入正确的上下文中。(你可以用 RDFa by HTML 来做到这一点。
仅此而已,不多不少。
现在让我们通过我最喜欢的 2 种 JSON 媒体类型查看您当前的选项(还有很多其他 JSON 超媒体类型,如 collection+json、shiren 等(。
HAL+JSON
通过 HAL,您可以定义嵌入式资源,并添加指向它们的链接。
{
"_links": {
.. *snip* ..
},
"_embedded": {
"manufacturer": {
"_links": {
"self": { "href": "/manufacturers/328764" },
"homepage": { "href": "http://hoverdonkey.com" }
},
"name": "Manufacturer Inc."
},
"review": [
{
"_links": {
"self": { "href": "/review/126" },
"customer": { "href": "/customer/fred", "title": "Fred Wilson" }
},
"title": "Love it!",
"content": "I love this product. I also bought a Hover Donkey with it.",
"rating": 10
},
...
]
},
"name": "A product",
"weight": 400,
.. *snip* ..
}
_link在这里描述 1.(,href 在这里描述 2.(,链接标题描述 6.(,链接关系描述 3.(和 8.我们需要内容类型 4.(,以及有关所需字段的元数据:验证、标签 5.(和 7.
现在我们在这里有什么选择?
- 我们可以在某种程度上扩展 HAL+JSON,并为每个链接添加一个字段属性,或者
- 我们可以添加供应商特定的内容类型来描述字段。
JSON-LD + 水螅词汇
通过 hydra 词汇表,您可以在 RDF(通常是 JSON-LD(文档中向hydra:Link
添加操作。
{
"@context": {
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"hydra": "http://www.w3.org/ns/hydra/core#",
"vocab": "/vocab#",
...
"title": {
"@id": "vocab:Issue/title",
"@type": "hydra:property",
"rdfs:range": "xsd:string",
"rdfs:label": "Issue title"
},
"comments": {
"@id": "vocab:comments",
"@type": "hydra:Link",
"hydra:supportedOperation": [
{
"@id": "vocab:create-comment",
"@type": "hydra:CreateResourceOperation",
"rdfs:label": "Creates a new comment",
"hydra:method": "POST",
"hydra:expects": "vocab:Comment",
"hydra:returns": "vocab:Comment"
}
]
}
},
"@id": "/issues/cso29ax",
"id": "cso29ax",
"created_at": "2012-12-10 12:45",
...
"title": "Some random issue",
"comments": {
"@id": "/issues/cso29ax/comments/"
}
}
实际上,您将整个@context部分移动到IRI /vocab
下的单独文件中,但是以这种方式检查它的作用更容易。通过RDF文档,你有主语,谓词,目标三元组。例如:/issues/cso29ax
、/ctx#Issue/title
、"Some random issue"
可以是三元组。因此,"id","created_at","标题","注释"只是您可以使用@context
描述的替代名称。
因此,在这里,@context
描述了表示的上下文和属性名称(如/ctx#create-comment
(和操作名称(如hydra:CreateResourceOperation
描述链接关系 8(。Ofc 如果需要,您可以通过/ctx#create-comment
的描述使用iana
词汇。这取决于你和 REST 客户端的功能。hydra:Link
描述它是一个表格 1。@id: "/issues/cso29ax/comments/"
描述了操作 IRI 2。rdfs:label
描述了表单标题和输入字段标签 6.(和 7.((它可以是多语言的(。内容类型始终为 JSON+LD 4。HTTP 方法由 hydra:method
3.(、验证数据等描述。由每个属性的hydra:expects
和hxdra:returns
描述 5.(所以它是"@context"的一部分(我在示例中省略了它(。因此,通过RDF + hydra词汇,您不需要任何其他内容来描述您的表单。
您可能需要的是定义同一资源的多个表示形式。
例如,您需要一个带有创建链接、编辑链接、删除链接等的表示,并且您需要一个仅包含数据的表示。您可以通过多种方式执行此操作:
- 您可以为每个表示形式定义供应商特定的内容类型。
- 您可以为资源添加新的 IRI,例如
/issues/?data-only=1
。(查询适用于 IRI 的非分层部分,因此它也是资源标识符的一部分。单个资源可以有多个标识符,因此/issues/
和/issues/?data-only=1
可以具有相同的资源。单个标识符不能属于多个资源,但我认为这是显而易见的。因此,在您的情况下,您不必创建编写器和编辑器资源,拥有具有多个标识符 (IRI( 的单个资源就足够了。