如何在REST API中为进度“资源”建模?

时间:2019-06-19 13:14:07

标签: rest json-api

我有以下数据结构,其中包含一个sectionIds数组。它们按照完成的顺序存储:

applicationProgress: ["sectionG", "sectionZ", "sectionA"]

我希望能够执行以下操作:

GET /application-progress-预期:G部分,Z部分,A部分

GET /application-progress?filter[first]=true-预期:sectionG

GET /application-progress?filter[current]=true-预期:A节

GET /application-progress?filter[previous]=sectionZ-预期:sectionG

我感谢上述网址不正确,但是我不确定如何命名/结构化它们以获取预期数据,例如这里的资源是“ sectionids”吗?

我想遵守JSON:API规范。

更新

我希望遵守JSON:API v1.0

就资源而言,我相信我拥有“部分”和“ ProgressEntry”。每个ProgressEntry与一个Section都具有一对一的关系。

我希望能够在集合中进行查询,例如

获取集合中的第一项:

GET /progress-entries?filter[first]

返回:

{
    "data": {
        "type": "progress-entries",
        "id": "progressL",
        "attributes": {
            "sectionId": "sectionG"
        },
        "relationships": {
            "section": {
                "links": {
                    "related": "http://example.com/sections/sectionG"
                }
            }
        }
    },
    "included": [
        {
            "links": {
                "self": "http://example.com/sections/sectionG"
            },
            "type": "sections",
            "id": "sectionG",
            "attributes": {
                "id": "sectionG",
                "title": "Some title"
            }
        }
    ]
}

在给定相对ProgressEntry的情况下获取先前的ProgressEntry。因此,在以下示例中,找到一个sectionId属性等于“ sectionZ”的ProgressEntry,然后获取上一个条目(sectionG)。在此之前,我不清楚这个过滤是基于ProgressEntry的属性的:

GET /progress-entries?filter[attributes][sectionId]=sectionZ&filterAction=getPreviousEntry

返回:

{
    "data": {
        "type": "progress-entries",
        "id": "progressL",
        "attributes": {
            "sectionId": "sectionG"
        },
        "relationships": {
            "section": {
                "links": {
                    "related": "http://example.com/sections/sectionG"
                }
            }
        }
    },
    "included": [
        {
            "links": {
                "self": "http://example.com/sections/sectionG"
            },
            "type": "sections",
            "id": "sectionG",
            "attributes": {
                "id": "sectionG",
                "title": "Some title"
            }
        }
    ]
}

1 个答案:

答案 0 :(得分:0)

我开始评论jelhan的答复,尽管我的答复只是想就他的反对意见作出合理的评论,因此我将其包括在这里,因为无论如何它或多或少为答复提供了很好的介绍。

资源由唯一标识符(URI)标识。 URI通常独立于任何表示格式,否则内容类型协商将毫无用处。 json-api是一种媒体类型,它定义了为特定资源交换的表示形式的结构和语义。媒体类型不应该对资源的URI结构施加任何约束,因为它独立于资源。即使URI包含类似vnd.api+json之类的内容,也无法根据给定URI推断要使用的媒体类型,因为这可能只是一个讨论json:api的网页。如果服务器支持两种表示形式,则客户端也可以在同一URI上请求application/hal+json而不是application/vnd.api+json,并接收只是打包成不同表示形式语法的相同状态信息。

如jelhan所述,

配置文件只是实际媒体类型的扩展机制,它允许通用媒体类型通过添加更多约束,约定或扩展来专门化。这样的配置文件使用与XML名称空间类似的URI,并且不需要,但应该取消引用那些URI以允许访问进一步的文档。除了Web Linking之外,没有任何关于实际资源的URI的讨论,URI可能会提示客户端使用的媒体类型,我不建议这样做,因为这需要客户端对它有一定的了解。提示。

正如我在最初的评论中提到的那样,URI不应该传达语义,因为存在链接关系!

链接关系

通过这种方式,您所概述的资源似乎是一些其他资源的集合,按您的领域语言分类。尽管json:api中定义的pagination不能直接完美地映射到此处,除非您有太多的节要分割成多个页面,否则可以使用{定义的标准化链接关系来使用相同的概念{3}}。

在这里,一台服务器可能会向您提供指向收集资源的链接,该链接可能看起来像这样:

{
  "links": {
    "self": "https://api.acme.org/section-queue",
    "collection": "https://api.acme.org/app-progression",
    ...
  },
  ...
}

由于IANA标准化了collection链接关系,您知道该资源可能包含条目的集合,这些条目在调用时可能会返回json:api表示形式,例如:

{
  "links": {
    "self": "https://api.acme.org/app-progression",
    "first": "https://api.acme.org/app-progression/sectionG",
    "last": "https://api/acme.org/app-progression/sectionA",
    "current": "https://api.acme.org/app-progression",
    "up": "https://api.acme.org/section-queue",
    "https://api/acme.org/rel/section": "https://api.acme.org/app-progression/sectionG",
    "https://api/acme.org/rel/section": "https://api.acme.org/app-progression/sectionZ",
    "https://api/acme.org/rel/section": "https://api.acme.org/app-progression/sectionA",
    ...
  },
  ...
}

,您在其中有更多链接可以在层次结构中上移或下移,或者选择完成的第一个或最后一个部分。请注意,最后3个示例URI利用了RFC 5988(网络链接)定义的IANA

在进一步深入层次结构时,您可能会找到诸如

的链接
{
  "links": {
    "self": "https://api.acme.org/app-progression/sectionZ",
    "first": "https://api.acme.org/app-progression/sectionG",
    "prev": "https://api.acme.org/app-progression/sectionG",
    "next": "https://api.acme.org/app-progression/sectionA",
    "last": "https://api.acme.org/app-progression/sectionA",
    "current": "https://api.acme.org/app-progression/sectionA",
    "up": "https://api.acme.org/app-progression",
    ...
  },
  ...
}

该示例仅说明服务器如何为您提供客户端可能需要完成其任务的所有选项。它将仅跟随其感兴趣的链接。基于所提供的链接关系名称,客户可以就所提供的链接是否感兴趣进行明智的选择。如果知道资源是一个集合,则可能遍历所有元素并逐个(或同时由多个线程)处理它们。

这种方法在Internet上非常普遍,并且允许服务器随时间轻松更改其URI方案,因为客户端将仅对链接关系名称起作用,并且仅调用URI,而不会尝试从中推断出任何逻辑。此技术还可以轻松用于其他媒体类型,例如application/hal+json等,并且默认情况下允许每个相应的资源进行缓存和重用,这可能会减轻服务器的负载,具体取决于它必须处理的查询。

请注意,该部分的实际内容尚未说过。它可能是各节所特有的内容的复杂摘要,也可能只是一个字。我们可以对其进行分类并为其命名,因为即使是一个简单的单词也可以成为资源的有效目标。此外,如extension relation types mechanism所述,您通过HTTP(REST)公开的资源和域模型并不相同,并且通常不会一对一映射。

过滤

json:api允许通过定义Jim Webber在语义上将参数分组在一起。如果使用内容类型协商来同意json:api作为表示形式,则互操作性问题的可能性就很小,尽管如果主动发送这样的表示,则此类查询参数的解析可能会失败。

重要的是要提到,在REST体系结构中,客户端应仅使用服务器提供的链接,而不能自行生成链接。客户端通常对URI不感兴趣,但对URI的内容不感兴趣,因此服务器需要知道如何对URI进行操作。

可以使用概述的建议,也可以使用以下形式的URI

.../application-progress?filter=first
.../application-progress?filter=current
.../application-progress?filter=previous&on=sectionZ
可以使用

代替。除此之外,这种方法还应该适用于几乎所有客户端,而无需更改其URL编码的解析机制。除此以外,还可以最小化返回生成的其他媒体类型的URI的管理开销。请注意,上面示例中的每个URI代表它们自己的资源,并且缓存将基于用于检索此类结果的URI存储对此类资源的响应。像.../application-progress?filter=next&on=sectionG.../application-progress?filter=previous&on=sectionA这样的查询会检索基本相同的表示形式,这是两个独特的资源,将由您的API两次处理两次,因为第一个查询的响应不能用作缓存键(URI ) 是不同的。根据Fielding customized x-www-form-urlencoded parsing的说法,REST是为数不多的约束之一,必须遵守这些约束,否则您将违反它。

如何设计此类URI完全取决于您。重要的是,如何教客户端何时调用此类URI,以及何时不调用此类URI。同样,这里可以使用链接关系。

摘要

总而言之,您更喜欢哪种方法以及您选择的URI样式。客户端(尤其是在REST环境中)不关心URI的结构。他们对链接关系进行操作,并仅使用URI来调用它以继续执行任务。因此,如Jim Webber所提到的那样,服务器API应该通过caching的形式来教客户如何了解知识,从而帮助客户。如AsbjørnUlsberg所述,将交互模型设计为text-based computer game in the 70/80's很有帮助。

尽管您可以对json:api提供的分组参数进行过滤,但此类链接仅可在json:api表示形式中使用。如果将此类链接复制并粘贴到浏览器或其他某个频道,则该客户端可能无法处理该链接。因此,这不是我的第一选择,TBH。在这里,您还可以选择是否将节设计为它们自己的资源或只是要检索的属性。我们真的不知道您的域模型中的哪些部分,IMO听起来像是有效资源,尽管可能具有或没有进一步的属性。