仅根据聚合根更新 RESTful 资源

Updating RESTful resources against aggregate roots only

通常我架构 RESTful APIs 使用以下资源 URI 方案:

POST /products
PATCH /products/{id}
GET /products
GET /products/{id}
DELETE /products/{id}

产品还可能包含产品特性。当我想获得某些产品的功能时,我会执行 GET /products/{id}/features.

顺便说一句,如果我想向给定产品添加新功能,通常我不会提供这样的资源 URI:PATCH /products/{id}/features 但我认为 features 是给定产品的一部分产品,因此,我更新哪些功能可能包含如下功能:

PATCH /products/{id}

{
    "features": {
         "add": [1, 2, 3]
    }
}

另一方面,如果我想更新某些功能元数据,我不会使用 产品资源,但我会执行这样的请求:

PATCH /products/features/{id}

{
    title: "Test"
}

在我的例子中,产品特性与特定产品无关,但它们可以与许多产品相关联。

理想情况下,我应该更新哪些功能拥有给定产品,向 /products/{id}/features 发出 PATCH 请求,顺便说一句,它会使服务器变得过于复杂 API,因为您需要分别涵盖所有实体的聚合.

我担心的是,是否可以考虑将某些给定聚合根的关联作为实体本身的一部分进行更新。

有关该主题的更多背景知识

归根结底,有人可能会说像这样的 API 并不完全 RESTful,因为我不应该期望使用 [= 删除某些给定产品的功能17=] 动词但是 DELETE: DELETE /products/{id}/features/{featureId},从客户端的角度来看,这比使用 DTO 修补产品更容易 API 用法.

现在您的架构还不是很干净。一方面,你实现了restful的思维方式,但另一方面,像

这样的事情
PATCH /products/{id}

{
    "features": {
         "add": [1, 2, 3]
    }
}

PATCH /products/features/{id}

{
    title: "Test"
}

不直观。

对于第一个例子,我会推荐

PATCH /products/{id}

{
    "features": [
        {
            "id": 1
        },
        {
            "id": 2
        },
        {
            "id": 3
        },
        {
            "id": 4
        }
    ]
}

您为该产品提供所有 功能。您不能只定义自己的元素 add,它会向产品添加给定的功能。更新资源的结构应该与您使用 GET /products/{id} 获得的结构相当(我猜您没有获得 add 属性,是吗?)。

对于第二个,您的 url 应该是 /product-features/{feature_id} 或只是 /features/{feature_id}。不要用 /products/features/{feature_id} 打破模式 /products/{product_id}。你为什么要这样想?这是合乎逻辑的 - 当您 GET /products 时,您不会获得包含所有功能列表的资源

{
    ...
    "features": [
        {
             "id": 1
        },
        ...
    ]
    ...
}

而是所有产品的列表

{
     [
          {
               ....
               "id": 1,
               "features": ...
          },
          ...
     ]
}

回答你的问题,如果你按照我在 restful 中建议的方式正确实施它,更新产品的功能绝对可以用这种方法。


编辑:

About the thing of /products/features or /product-features, is there any consensus on this? Do you know any good source to ensure that it's not just a matter of taste?

我认为这是一种误导。我希望获得所有产品的所有功能,而不是获得所有可能的功能。但是,老实说,很难找到直接谈论这个问题的任何来源,但是有很多文章人们不尝试创建嵌套资源,例如 /products/features,而是这样做 separately .

About the thing of add when patching, it's the easiest way I've found to express that I'm adding, updating or removing features from a given product. In fact, it's a DTO

如果你想对集合使用一些动作,有一个标准。看看https://www.rfc-editor.org/rfc/rfc6902#appendix-A.2。而不是

PATCH /products/{id}

{
    "features": {
         "add": [1, 2, 3]
    }
}

你会发送

PATCH /products/{id}

{
    "op": add, "path": "/features/0", "value": 1,
    "op": add, "path": "/features/0", "value": 2,
    "op": add, "path": "/features/0", "value": 3
}

BTW, note that you didn't answer the core issue in my question.

作为 everything can be considered as sub-resource, I don't see any conflict if product's features, as a collection, is an attribute of the product and also it's considered as sub-resource to enable POST for example. If one's find it not safe, then you can cut it to operate 仅在 sub-resource.

此外,在这篇article中,作者批准了使用PATCH更新sub-resources的方式,简化了过程(整篇文章有点争议,但还没有结束我们感兴趣的事情)

Another solution is to expose the resource’s properties you want to make editable, and use the PUT method to send an updated value. In the example below, the email property of user 123 is exposed:

PUT /users/123/email
 
new.email@example.org

While it makes things clear, and it looks like a nice way to decide what to expose and what not to expose, this solution introduces a lot of complexity into your API (more actions in the controllers, routing definition, documentation, etc.). However, it is REST compliant, and a not-so-bad solution, but there is a better alternative: PATCH