我有一个具有通常结构的 REST API
/customers // get a list of all customers
/customers/22 // get customer with the ID 22
/customers/22/orders // get all orders for customer 22
/customers/22/orders/156 // get order 156 for customer 22
我有简单的客户和订单 DTO。订单不是客户 DTO 的一部分。每次检索 Customer 对象时,您不一定需要所有客户订单的列表。因此,如果您确实想要客户对象及其所有报告的列表,您可以对 API 进行两次调用。
但是 Customer 对象也有几个非常简单的子对象的集合,例如电话号码、地址等。我已将这些部分作为客户 DTO 的一部分,并且我尚未提供子记录的端点。
例如,
GET /customers/22
可能会让你
{
"id": 22,
"forename": "Jack",
"surname": "Smith",
"telephones": [
{
"id": 45,
"type": "mobile",
"number": "[phone number here]"
},
{
"id": 46,
"type": "home",
"number": "[phone number here]"
}
],
"emails": [
{
"id": 226,
"type": "personal",
"number": "[address here]"
},
{
"id": 242,
"type": "work",
"number": "[address here]"
}
],
"addresses": [
{
"id": 35,
"type": "home",
"house": "24",
"street": "Hyacinth Avenue",
"town": "London"
},
{
"id": 7,
"type": "work",
"house": "54",
"street": "Hydrangea Road",
"town": "Nottingham"
}
]
}
所有子对象都有自己的唯一标识符。
我对处理客户记录更新的方式并不完全满意。我接受
POST /customers/22
在正文中,我期望完整的 Customer 对象,包括所有子记录。服务器计算出哪些(如果有)子记录已被创建、更新或删除,并对数据库进行适当的更改。
这有几个问题。
如果您只想更改主客户记录上的某些内容(例如姓氏的拼写),则必须将所有子记录作为更新请求的一部分提交,这很麻烦。
如果您提交了以下内容
POST /customers/22
body:
{
"id": 22,
"forename": "Jack",
"surname": "Smyth"
}
然后,除了更新姓氏之外,API 还会将其解释为您想要删除所有电话号码、电子邮件地址和地址。 API 文档中已经明确说明了这一点,但这感觉有点像一场即将发生的灾难。
我认为最好以与任何其他子资源完全相同的方式处理子对象,并为每个子对象提供单独的 URI,例如
/customers
/customers/22
/customers/22/telephones
/customers/22/emails
/customers/22/addresses
并接受通常的 GET、POST 和 PUT。
因此,如果您想要“完整”的客户记录,则必须对 API 进行四次调用。这并不理想,但如果它能让 API 更清晰并且不易发生事故,我就同意了。
另一个含义是,如果最终用户同时更改客户端记录中的多个内容,则客户端有责任确定需要执行哪些插入、更新和删除操作,而不是让 API 工作这一切都为你准备好了。不过我对此很满意。
更改设计的最后一个问题是,目前,如果用户要更新姓氏、添加电话号码、删除电子邮件地址以及更新街道地址,所有这些都将通过一次发送到 API调用,并且在数据库上,所有更新都将在同一事务中完成。我们使用 SQL Server 系统版本表来跟踪客户记录的所有更改。查看客户记录更改历史的能力是该系统的一个非常重要的功能。我们称之为客户时间表。
如果我要更改为新设计,他们将看到一个接一个的四个较小的事件,而不是客户时间线中的一个事件。从最终用户的角度来看,他们可能已经一起进行了所有这些更改,因此他们希望看到包含所有更改的单个时间线事件。
由于 SQL Server 系统版本表的工作方式,我无法手动设置记录上的“ValidFrom”值。如果我希望它们都具有相同的 ValidFrom 日期,那么它们 have 都位于同一个数据库事务中。
我也许可以稍后再处理,例如当用户查询客户的时间线时,我将彼此相隔几秒内的所有事件分组在一起,但这感觉有点模糊。
是否有一种通用方法允许客户端将多个 API 调用捆绑到一个调用中,以表明它们都应该作为同一事务的一部分进行处理?
您可以创建一个 BATCH 端点。但也有一些缺点
简而言之:
优点:
确保多个操作之间的原子性和一致性。
让服务器在一次调用中处理插入、更新和删除,避免多次往返。
由于所有操作都捆绑在一起,因此简化了客户端逻辑。
缺点:
增加了 API 的复杂性。
需要仔细记录,因为每个操作都需要明确的验证和错误处理。
那看起来怎么样?
你这样称呼它:
POST /customers/22/batch
您的请求正文可以如下所示
{
"operations": [
{ "action": "update_customer", "data": { "surname": "Doe" } },
{ "action": "add_telephone", "data": { "type": "mobile", "number": "0815" } },
{ "action": "delete_email", "id": 123},
{ "action": "update_address", "id": 1, "data": { "street": "Some street" } }
]
}
服务器将确保所有更改都得到处理或根本不处理