【问题标题】:Hypermedia-friendly REST pattern for creating and updating a resource用于创建和更新资源的超媒体友好 REST 模式
【发布时间】:2014-10-22 18:38:29
【问题描述】:

我正在尝试设计一个充分利用超媒体的 RESTful 服务。
最好,用户代理应该只知道根 URI,以便能够探索服务的所有功能 - 也就是说,我希望它位于 maturity model 的第 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 资源中包含一个 linkfooWriter

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

总而言之,任何可以创建和编辑的资源都可能具有关联的writer资源。
通过 GET-ting writer,用户代理可以非常方便地创建/编辑资源。
写入器中嵌入的 payload 被 POST 到它的 destination 并且瞧 :)

此外,还应该有一个 root 容器,其中包含指向资源及其新资源编写器的链接(请参阅上面示例中的 fooRoot)。



问题是……

...上面描述的模式有一个众所周知的名字吗?
...有没有更好的方法来解决 create / edit 问题,其中 create / edit时间和第三级成熟度仍然“成立”?

一些参考资料:

【问题讨论】:

  • 为什么foo 资源中没有包含自链接,为什么不通过对自链接执行 PUT 来简单地更新资源?这似乎比你的方法少了很多工作。
  • @JonathanW,谢谢 :) 但是,“colorEnum”、“categoryEnum”等的链接呢?我也应该将它们添加到 foo 资源中吗?如何创建一个新的 foo,而不是编辑现有的?在这种情况下,这些链接会嵌入到哪里?
  • 因此,您可能没有得到回复的部分原因是您最初的问题是非常开放的。为了直接回答你的问题,这个模式没有我知道的名字。至于模式是否“好的”,我不确定你的意思。模式只是用于设计某些东西的模板,根据定义,它通常是适用的。那么...这真的是您想要回答的问题,还是 this 模式是否是解决您的问题的最佳方式的问题?
  • @JonathanW,再次感谢 :) 我已经更新了帖子末尾的问题。我想最重要的是我需要一些关于所描述解决方案的社区反馈。它看起来可重用 & 普遍适用,它适合一系列常见问题,所以我们可以称之为模式 .但它没有名字:)
  • 如果枚举是一组静态的可能性,我只会记录可能的值并在服务器上验证。我不会尝试将它作为链接烘焙到交换中......但如果有很多信息并且枚举本身指向不同的资源,你可以像我在示例中对欧洲防风草所做的那样做类似的事情.

标签: rest design-patterns web restful-architecture hypermedia


【解决方案1】:

你的描述让我想起了create and edit form link relations。但是,如果您正在构建一个 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 集合资源并通过其链接与欧洲防风草相关:

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 简单但功能强大。我真正喜欢的约定之一是使用关系名称 as 一个 URI,当取消引用时,它直接指向有关该关系及其可以返回的资源的文档。

如果您想使用这样的实时 API,我强烈建议您查看 HALtalk,这是 HAL 的实时演示 API。

【讨论】:

【解决方案2】:

让我们问一个问题。编辑资源需要什么?

  • 对于 HTML,您需要一个 FORM 标记和其中的 INPUT 标记。

他们描述了什么?

  • 1.) 它是一种形式。
  • 2.) 它有一个动作 IRI。
  • 3.) 它有一个方法。
  • 4.) 它具有内容类型。
  • 5.) 它有多个字段,应由用户填写。也许有验证数据。

以你得到的形式:

  • 6.) 整个表单的标题
  • 7.) 输入字段的标签

这与 REST 客户端所需的几乎相同。

  • 8.) 它应该包含它已经知道的 REST 客户端的链接关系,因此客户端将使用它来将表单放入正确的上下文中。 (您可以通过 HTML 使用 RDFa 来做到这一点。)

仅此而已,不多也不少。

现在让我们通过我最喜欢的 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 词汇

通过hydra 词汇,您可以在RDF(通常是JSON-LD)文档中向hydra:Links 添加操作。

{
    "@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”、“title”、“cmets”只是你可以用@context描述的替代名称。

所以在这里@context 描述了表示的上下文和/ctx#create-comment 之类的属性名称和hydra:CreateResourceOperation 之类的操作名称描述了链接关系 8.)。如果需要,您可以通过 /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:expectshxdra:returns 描述 5.) 所以它是“@context”的一部分(我在示例中省略了它)。因此,通过 RDF + hydra vocab,您不需要任何其他内容来描述您的表单。

您可能需要定义同一资源的多个表示形式。

例如,您需要一个包含创建链接、编辑链接、删除链接等的表示,而您只需要一个包含数据的表示。您可以通过多种方式做到这一点:

  • 您可以为每个表示定义供应商特定的内容类型。
  • 您可以为您的资源添加新的 IRI,例如 /issues/?data-only=1。 (查询是针对 IRI 的非分层部分,因此它也是资源标识符的一部分。单个资源可以有多个标识符,因此 /issues//issues/?data-only=1 可以具有相同的资源。A单个标识符不能属于多个资源,但我认为这很明显。)因此,在您的情况下,您不必创建编写器和编辑器资源,拥有具有多个标识符 (IRI) 的单个资源就足够了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-01
    • 2012-09-06
    • 2020-12-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-30
    • 1970-01-01
    • 2010-09-18
    相关资源
    最近更新 更多