原子操作

此扩展提供了一种以线性且原子方式执行多个“操作”的方法。操作是 JSON:API 基本规范中允许的突变的序列化形式。

客户端可以在单个请求中发送一个操作数组。此扩展保证这些操作将按顺序处理,并且将完全成功或一起失败。

URI

此扩展的 URI 为 https://jsonapi.fullstack.org.cn/ext/atomic

命名空间

此扩展使用命名空间 atomic

注意:JSON:API 扩展只能使用保留命名空间作为前缀来引入新的文档成员。

文档结构

支持此扩展的文档**可以**包含基本规范允许的任何顶级成员,但 dataincluded 除外,**不能**包含。

此外,此类文档**可以**包含以下两个成员之一,但不能同时包含:

  • atomic:operations - 一个包含一个或多个 操作对象 的数组。

  • atomic:results - 一个包含一个或多个 结果对象 的数组。

如果存在 atomic:operationsatomic:results,则**不能**在同一文档中包含 errors 成员。

操作对象

操作对象**必须**包含以下成员:

  • op:一个操作代码,表示为字符串,指示要执行的操作类型。该值**必须**是以下之一:

    • "add":创建新资源或关系
    • "update":更新资源或关系
    • "remove":删除资源或关系

操作对象**可以**包含以下两个成员之一,但不能同时包含,以指定操作的目标:

  • ref:一个对象,**必须**包含以下成员组合之一:

    • typeid:以针对单个资源。
    • typelid:以针对在先前操作对象中分配了本地标识 (lid) 的单个资源。
    • typeidrelationship:以针对单个资源的关系。
    • typelidrelationship:以针对在先前操作对象中分配了本地标识 (lid) 的单个资源的关系。
  • href:一个包含 URI 引用 [RFC3986 第 4.1 节] 的字符串,用于标识操作的目标。

某些特定类型的操作需要包含 refhref,如下所述。

操作对象还可以包含以下任何成员:

  • data:操作的“主要数据”。

  • meta:一个 元对象,其中包含有关操作的非标准元信息。

不同成员需要处理不同类型的操作,如下所述。

结果对象

操作结果对象**可以**包含以下任何成员:

  • data:操作产生的“主要数据”。

  • meta:一个 元对象,其中包含有关结果的非标准元信息。

空结果对象 ({}) 对于不需要返回 data 的操作是可接受的。

处理

所有使用此扩展发送的 HTTP 请求**必须**使用 POST 发出。

服务器**必须**按照它们在 atomic:operations 数组中出现的顺序执行操作。

服务器**必须**以原子方式执行所有操作,因此,如果无法执行任何操作,**必须**使所有先前操作的效果无效。

如果返回响应文档,服务器**必须**使用 200 OK 响应成功操作请求。一个 结果对象 数组**必须**在顶级 atomic:results 成员中返回。结果数组**必须**与请求的操作数组长度相同,并且每个结果**必须**按位置与其关联的操作对应。

如果请求的操作不需要返回 data,服务器**可以**使用 204 No Content 和无响应文档响应成功的请求。

处理错误

如果请求中的任何操作失败,服务器**必须**按照 基本规范中的说明 响应。**应该**返回一个或多个 错误对象 数组,每个错误对象都有一个 source 成员,其中包含指向请求文档中问题来源的 pointer

如果请求的操作格式错误或不完整,则服务器**必须**使用 400 Bad Request 响应,并且**应该**包含一个带有顶级 errors 成员的文档,该成员包含一个错误对象。错误对象**应该**包含一个 source 成员,其中包含指向无效操作的 pointer

如果操作格式正确,但服务器无法处理,则服务器**必须**使用 400 Bad Request 或更合适的错误响应(例如 409 Conflict422 Unprocessable Entity)响应,并且**应该**包含一个带有顶级 errors 成员的文档,该成员包含一个或多个错误对象,提供更多详细信息。

处理特定操作

以下部分介绍如何处理特定操作。

创建资源

创建资源的操作**可以**通过操作的 href 成员来定位资源集合。

操作**必须**包含一个 op 代码为 "add" 以及作为 data 的资源对象。资源对象**必须**至少包含一个 type 成员。

例如

POST /operations HTTP/1.1
Host: example.org
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:operations": [{
    "op": "add",
    "href": "/blogPosts",
    "data": {
      "type": "articles",
      "attributes": {
        "title": "JSON API paints my bikeshed!"
      }
    }
  }]
}

请注意,在本例中,href 用于定位资源集合(在本例中为 blogPosts),该集合与资源的 type 不同。对 href 的使用对于此扩展完全是可选的,但可能由单个实现要求。

响应

如果服务器能够使用客户端生成的 ID 创建资源,并且其表示形式与操作中的资源相同,则服务器**可以**返回一个没有 data 成员的结果,或者**可以**返回一个包含已创建资源作为 data 的结果。

在服务器能够成功创建资源的所有其他情况下,服务器**必须**返回一个包含已创建资源作为 data 的结果。

例如

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:results": [{
    "data": {
      "links": {
        "self": "http://example.com/blogPosts/13"
      },
      "type": "articles",
      "id": "13",
      "attributes": {
        "title": "JSON API paints my bikeshed!"
      }
    }
  }]
}

如果服务器无法创建资源,**必须**返回适当的错误响应,并且**应该**返回包含顶级 errors 的响应文档,如 上文所述

更新资源

更新资源的操作**可以**通过操作的 refhref 成员来定位该资源,但不能同时包含两者。

操作**必须**包含一个 op 代码为 "update"

例如

POST /operations HTTP/1.1
Host: example.org
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:operations": [{
    "op": "update",
    "data": {
      "type": "articles",
      "id": "13",
      "attributes": {
        "title": "To TDD or Not"
      }
    }
  }]
}
响应

如果服务器接受更新,但也以请求中未指定的方式更改资源(例如,更新 updatedAt 属性或计算的 sha),则服务器**必须**返回一个包含已更新资源表示形式作为 data 的结果。

如果服务器接受更新,并且除了提供的那些字段之外没有更新任何字段,则服务器**必须**返回一个包含没有 data 或资源表示形式作为 data 的结果,或者,如果所有结果都为空,则服务器**可以**使用 204 No Content 和无文档响应。

如果服务器无法更新资源,**必须**返回适当的错误响应,并且**应该**返回包含顶级 errors 的响应文档,如 上文所述

删除资源

删除资源的操作**必须**通过操作的 refhref 成员来定位该资源,但不能同时包含两者。

操作**必须**包含一个 op 代码为 "remove"

例如

POST /operations HTTP/1.1
Host: example.org
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:operations": [{
    "op": "remove",
    "ref": {
      "type": "articles",
      "id": "13"
    }
  }]
}
响应

如果服务器能够删除资源,则服务器**必须**返回一个没有 data 的结果,或者,如果所有结果都为空,则服务器**可以**使用 204 No Content 和无文档响应。

如果服务器无法删除资源,则**必须**返回相应的错误响应,并且**应该**返回一个包含顶级errors的响应文档,如上所述

更新一对一关系

更新资源的一对一关系的操作**必须**通过操作的refhref成员来定位该关系,但不能同时使用两者。

操作**必须**包含一个 op 代码为 "update"

例如,以下请求分配了一对一关系

POST /operations HTTP/1.1
Host: example.org
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:operations": [{
    "op": "update",
    "ref": {
      "type": "articles",
      "id": "13",
      "relationship": "author"
    },
    "data": {
      "type": "people",
      "id": "9"
    }
  }]
}

以下请求清除了一对一关系

POST /operations HTTP/1.1
Host: example.org
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:operations": [{
    "op": "update",
    "ref": {
      "type": "articles",
      "id": "13",
      "relationship": "author"
    },
    "data": null
  }]
}
响应

如果服务器能够更新关系,则服务器**必须**返回一个没有data的结果,或者,如果所有结果为空,则服务器**可以**响应204 No Content,并且不返回任何文档。

如果服务器无法更新关系,则**必须**返回相应的错误响应,并且**应该**返回一个包含顶级errors的响应文档,如上所述

更新一对多关系

更新资源的一对多关系的操作**必须**通过操作的refhref成员来定位该关系,但不能同时使用两者。

要向一对多关系添加成员,操作**必须**包含op代码"add"。例如

POST /operations HTTP/1.1
Host: example.org
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:operations": [{
    "op": "add",
    "ref": {
      "type": "articles",
      "id": "1",
      "relationship": "comments"
    },
    "data": [
      { "type": "comments", "id": "123" }
    ]
  }]
}

要替换一对多关系的所有成员,操作**必须**包含op代码"update"。例如

POST /operations HTTP/1.1
Host: example.org
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:operations": [{
    "op": "update",
    "ref": {
      "type": "articles",
      "id": "1",
      "relationship": "tags"
    },
    "data": [
      { "type": "tags", "id": "2" },
      { "type": "tags", "id": "3" }
    ]
  }]
}

要从一对多关系中删除成员,操作**必须**包含op代码"remove"。例如

POST /operations HTTP/1.1
Host: example.org
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:operations": [{
    "op": "remove",
    "ref": {
      "type": "articles",
      "id": "1",
      "relationship": "comments"
    },
    "data": [
      { "type": "comments", "id": "12" },
      { "type": "comments", "id": "13" }
    ]
  }]
}
响应

如果服务器能够更新关系,则服务器**必须**返回一个没有data的结果,或者,如果所有结果为空,则服务器**可以**响应204 No Content,并且不返回任何文档。

如果服务器无法更新关系,则**必须**返回相应的错误响应,并且**应该**返回一个包含顶级errors的响应文档,如上所述

处理多个操作

以上示例都执行单个操作,该操作与基本规范中相应的单个请求一致。然而,此扩展的主要价值在于它解锁了以线性且原子方式执行多个操作的能力。

以下示例在单个请求中添加两个资源并在它们之间创建关系

POST /operations HTTP/1.1
Host: example.org
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:operations": [{
    "op": "add",
    "data": {
      "type": "authors",
      "id": "acb2ebd6-ed30-4877-80ce-52a14d77d470",
      "attributes": {
        "name": "dgeb"
      }
    }
  }, {
    "op": "add",
    "data": {
      "type": "articles",
      "id": "bb3ad581-806f-4237-b748-f2ea0261845c",
      "attributes": {
        "title": "JSON API paints my bikeshed!"
      },
      "relationships": {
        "author": {
          "data": {
            "type": "authors",
            "id": "acb2ebd6-ed30-4877-80ce-52a14d77d470"
          }
        }
      }
    }
  }]
}

服务器可能会对该请求做出以下响应

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/atomic"

{
  "atomic:results": [{
    "data": {
      "links": {
        "self": "http://example.com/authors/acb2ebd6-ed30-4877-80ce-52a14d77d470"
      },
      "type": "authors",
      "id": "acb2ebd6-ed30-4877-80ce-52a14d77d470",
      "attributes": {
        "name": "dgeb"
      }
    }
  }, {
    "data": {
      "links": {
        "self": "http://example.com/articles/bb3ad581-806f-4237-b748-f2ea0261845c"
      },
      "type": "articles",
      "id": "bb3ad581-806f-4237-b748-f2ea0261845c",
      "attributes": {
        "title": "JSON API paints my bikeshed!"
      },
      "relationships": {
        "author": {
          "links": {
            "self": "http://example.com/articles/bb3ad581-806f-4237-b748-f2ea0261845c/relationships/author",
            "related": "http://example.com/articles/bb3ad581-806f-4237-b748-f2ea0261845c/author"
          }
        }
      }
    }
  }]
}

请注意,此操作请求也可以被构建为先添加作者,然后添加文章,最后添加它们之间的关系。

还需要注意的是,本地标识,即lid成员,对于涉及多个操作的请求特别有用。本地标识可用于关联尚未分配id的资源。