设计模式 - 通过REST API

时间:2015-07-02 15:10:50

标签: api rest

我正在努力使用REST API设计概念。我有这些课程:

user:
  - first_name
  - last_name

metadata_fields:
  - field_name

user_metadata:
  - user_id
  - field_id
  - value
  - unique index on [user_id, field_id]

好的,因此用户拥有许多元数据,元数据类型在metadata_fields中定义。典型的HABTM,在连接表中有额外的数据。

如果我要通过Rails表单更新user_metadata,数据将如下所示:

user_metadata: {
  id: 1,
  user_id: 2,
  field_id: 3,
  value: 'foo'
}

如果我发布给用户#update controller,数据将如下所示:

user: {
  user_metadata: {
    id: 1,
    field_id: 3,
    value: 'foo'
  }
}

这种方法的问题在于我们忽略了user_id / field_id关系的唯一性。如果我在任一更新中更改field_id,我不仅仅是更改数据,而是更改数据的含义。这在Rails中可以正常工作,因为它有点像围墙花园,但是当你打开一个API端点时会崩溃。

如果我允许的话:

PATCH /api/user_metadata

然后我将自己打开给修改user_id或field_id或两者的人。与此类似:

PATCH /api/user/:user_id/metadata

现在设置了user_id但是field_id仍然可以更改。所以解决这个问题的唯一方法是将更新限制为单个字段:

PATCH /api/user/:user_id/metadata/:field_id

或批量更新:

PATCH /api/user/:user_id/metadata

但是通过该调用,我们必须修改数据结构,以便user_id / field_id关系的唯一性保持不变:

user_metadata: {
  field_id1: 'value1',
  field_id2: 'value2',
  ...
}

我喜欢听到这里的想法。我已经搜索过谷歌并且一无所获。有什么建议吗?

1 个答案:

答案 0 :(得分:3)

由于元数据属于某个用户/api/user/{userId}/metadata/{metadataId},因此可能是用户的单个元数据资源的干净URI。资源的URI已经是您要查找的唯一键。不能有2个具有相同URI的资源!此外,URI已包含用户和字段ID。

GET /api/user/1 HTTP/1.1这样的请求可能会返回类似HAL的表示,如下所示:

{
    "user" : {
        "id": "1",
        "firstName": "Max",
        "lastName": "Sample",
        ... 
        "_links": {
            "self" : {
                "href": "/api/user/1"
            }
        },
        "_embedded": {
            "metadata" : {
                "fields" : [{
                    "id": "1",
                    "type": "string",
                    "value": "foo",
                    "_links": {
                        "self": {
                            "href": "/api/user/1/metadata/1"
                        }
                    }
                }, {
                    "id": "2",
                    "type": "string",
                    "value": "bar",
                    "_links": {
                        "self": {
                            "href": "/api/user/1/metadata/2"
                        }
                    }
                }],
                "_links": {
                    "self": {
                        "href": "/api/user/1/metadata"
                    }
                }
            }
        }
    }
}

当然,您可以发送PUTPATCH请求来修改现有元数据字段。但是,资源的URI仍然相同(除非您在PATCH请求中移动或删除资源)。

您还可以忽略导致PUT请求的某些字段,从而阻止修改某些字段,例如id_link。我假设这也应该对PATCH请求有效,但是因此必须重新阅读规范。

因此,我建议忽略请求中包含的所有id_link字段,并更新其余字段。但是,如果有人尝试更新ID字段,您还可以选择返回403 Forbidden409 Conflict响应。

<强>更新

如果要在单个请求中更新多个字段,则有两个选项:

  • 使用PUT并将当前字段集替换为新版本
  • 使用PATCH并向服务器发送必要步骤,将当前字段集转换为新字段集

示例PUT

PUT /api/user/1/metadata HTTP/1.1

{
    "metadata": {
        "fields": [{
            "type": "string", 
            "value": "newFoo"
        }, {
            "type": "string",
            "value": "newBar"
        }]
    }
}

此请求将首先删除元数据所属的用户的每个存储的元数据字段,然后为请求中的每个包含字段创建新的资源。虽然这仍然保证了唯一的URI,但是这种方法存在一些缺点:

  • 更新后应该可用的所有数据,甚至是不需要更改的字段,都需要传输
  • 具有指向特定资源的URI的客户端可能指向错误表示。 F.E.客户端在另一个客户端更新所有元数据之前检索到/user/1/metadata/2,通过自动递增调度ID,然而更新引入了新的第二个项目,因此将前者2移动到位置3,client1现在已经但是,实际数据为/user/1/metadata/2时,引用/user/1/metadata/3。为防止这种情况,可以使用唯一的UUID代替自动增量ID。如果客户端1稍后尝试检索或更新以前的资源2,则可以通知他该资源不再可用,甚至可以创建重定向到新位置。

示例PATCH

PATCH请求包含将资源状态转换为新状态的必要步骤。请求本身可以同时影响多个资源,甚至可以根据需要创建或删除其他资源。

以下示例采用json-patch+json格式:

PATCH /api/user/1/metadata HTTP/1.1

[
    { 
        "op": "add", 
        "path": "/0/value", 
        "value": "newFoo" 
    },
    { 
        "op": "add", 
        "path": "/2", 
        "value": { "type": "string", "value": "totally new entry" } 
    },
    { 
        "op": "remove", 
        "path": "/1" 
    },
]

路径被定义为被调用资源的JSON Pointer

JSON-Patch类型的add操作定义为:

  
      
  • 如果目标位置指定了数组索引,则会在指定索引处的数组中插入新值。
  •   
  • 如果目标位置指定了尚不存在的对象成员,则会向该对象添加新成员。
  •   
  • 如果目标位置指定了确实存在的对象成员,则替换该成员的值。
  •   

但是对于删除案例,规范说明:

  

如果从数组中删除元素,则指定索引上方的任何元素都会向左移动一个位置。

因此,新添加的条目将最终位于数组中的第2位。如果没有自动增量值用于ID,这应该不是一个大问题。

Besindes addremove规范还包含replacemovecopytest的定义。

PATCH应该是事务性的 - 要么所有操作都成功,要么都没有。规范说明:

  

如果JSON补丁文档违反了规范性要求,或者操作不成功,则JSON补丁文档的评估应该终止并且整个补丁文档的应用不会被视为成功。

我将此行解释为,如果它尝试更新不应更新的字段,则应该为整个PATCH请求返回错误,因此不会更改任何资源。

回归PATCH方法显然是交易要求以及JSON指针符号,它可能不那么受欢迎(至少我没有经常使用它并且不得不重新查找它)。与PUT相同,PATCH允许在现有资源之间添加新资源,并将更多资源转移到右侧,如果您依赖自动增量值,可能会导致问题。

因此,我强烈建议使用随机生成的UUID作为标识符而不是自动递增值。