首页 > 解决方案 > REST API - 单一与批量 POST / PATCH(SQL INSERT / UPDATE)

问题描述

我知道这个问题已经被问过很多次了,但我没有解决数据库 SQL 部分,也没有合适的例子。

目标是使用 REST API 在数据库中插入或更新发票,更具体地说,在发票中插入或更新项目。


数据

数据由两部分组成。

let invoiceHeader = {
  id: 123,
  key: "2019/00001",
  client: 42
}

let invoiceItems = [
  { id: 2001, sku: "A12345", quantity: 10, price: 300 },
  { id: 2002, sku: "B54321", quantity: 6, price: 500 }, 
  { id: 2003, sku: "C11223", quantity: 20, price: 200 },
]

数据库

该数据需要插入到 2 个数据库表中。

invoice
+-----+------------+--------+
| id  |    key     | client |
+-----+------------+--------+
| 123 | 2019/00001 |   42   |
+-----+------------+--------+

invoiceItems
+------+--------------+--------+----------+-------+
|  id  | fk_invoiceId |  sku   | quantity | price |
+------+--------------+--------+----------+-------+
| 2001 |          123 | A12345 |       10 |   300 |
| 2002 |          123 | B54321 |        6 |   500 |
| 2003 |          123 | C11223 |       20 |   200 |
+------+--------------+--------+----------+-------+

发票抬头

发票标题很简单

插入发票抬头(创建新发票)

POST http://example.com/api/invoices
{
  "key": "2019/00001",
  "client": 42
}

INSERT INTO invoice (key, client) VALUES ('2019/00001', 42);

更新发票抬头(使用新客户端)

PATCH http://example.com/api/invoices/123
{
  "id": 123,
  "client": 66
}

UPDATE invoice SET client = 66 WHERE id = 123;

发票项目

但是,发票项目可以通过 2 种方式完成。

方法 1 - 单人

插入发票项目

POST http://example.com/api/invoices/123/items
{ "sku": "A12345", "quantity": 10, "price": 300 }
INSERT INTO invoiceItems (sku, quantity, price) VALUES ('A12345', 10, 300);

POST http://example.com/api/invoices/123/items
{ "sku": "B54321", "quantity": 6, "price": 500 }
INSERT INTO invoiceItems (sku, quantity, price) VALUES ('B54321', 6, 500);

POST http://example.com/api/invoices/123/items
{ "sku": "C11223", "quantity": 20, "price": 200 }
INSERT INTO invoiceItems (sku, quantity, price) VALUES ('C11223', 20, 200);

更新发票项目(新数量和价格)

PATCH http://example.com/api/invoices/123/items/2001
{ "id": 2001, "quantity": 23, "price": 130 }
UPDATE invoiceItems SET quantity = 23, price = 130 WHERE id = 2001;

方法 2 - 散装

插入发票项目

POST http://example.com/api/invoices/123/items
[
  { "sku": "A12345", "quantity": 10, "price": 300 },
  { "sku": "B54321", "quantity": 6, "price": 500 }, 
  { "sku": "C11223", "quantity": 20, "price": 200 },
]

INSERT INTO invoiceItems (sku, quantity, price) 
VALUES 
  ('A12345', 10, 300),
  ('B54321', 6, 500),
  ('C11223', 20, 200)
;

更新发票项目(单个项目的新数量和价格)

PATCH http://example.com/api/invoices/123/items
[
  { "id": 2001, "sku": "A12345", "quantity": 23, "price": 130 },
  { "id": 2002, "sku": "B54321", "quantity": 6, "price": 500 }, 
  { "id": 2003, "sku": "C11223", "quantity": 20, "price": 200 },
]

INSERT INTO table (id, sku, quantity, price) 
VALUES 
    (2001, 'A12345', 10, 300),
    (2002, 'B54321', 6, 500),
    (2003, 'C11223', 20, 200)
ON DUPLICATE KEY UPDATE 
    sku = values(sku),
    quantity = values(quantity),
    price = values(price)
;

概括

单一方法发出大量请求,而批量方法发送不必要的数据。

请记住,一张发票可以轻松包含 300 多个项目。

那么,您认为哪种方法更好?

标签: mysqlresthttp

解决方案


为什么不:

POST /566f5944-ba10-4ff5-8061-1b6b04a436c6

{
   invoiceHeader : {
     id: 123,
     key: "2019/00001",
     client: 42
   }, 
   invoiceItems : [
     { id: 2001, sku: "A12345", quantity: 10, price: 300 },
     { id: 2002, sku: "B54321", quantity: 6, price: 500 }, 
     { id: 2003, sku: "C11223", quantity: 20, price: 200 },
   ]
}

请求中 target-uri 的拼写没有理由需要描述资源的语义。你可以这样做,它会让理解你的拼写约定的人更容易,但机器不在乎。

HTTP 消息中的有效负载是文档 - HTTP 是一种用于通过网络传输文档的应用程序协议(Jim Webber,2011 年)。它并不特别关心文档是什么,因此设计适合您的域需求的文档是有意义的。

该文档作为多个表的多行存储在服务器上的事实与 API 设计无关。这是 REST 的一部分,一切看起来都像一个网站。

我们可能关心的是资源的粒度及其对延迟和缓存的影响。大资源需要较少的往返,较小资源的图表为您提供更精细的缓存控制。

同样,想想网页——关于堆栈溢出的这个问题是一个“资源”,它包括指向具有不同缓存属性的其他资源(徽标和图标图像、java 脚本等)的超媒体链接。浏览器能够节省一些带宽,因为它知道(由于标准化的元数据)当前缓存的徽标副本仍然有效。

为什么 API 路由是哈希?

强调 target-uri 的拼写无关紧要:

POST /566f5944-ba10-4ff5-8061-1b6b04a436c6
POST /api/invoices
POST /invoices
POST /purpleMonkeyDishwasher

这些都很好

插入一个项目似乎有很多开销,所有的东西都随它一起发送。

正确的。因此,如果服务器有一个看起来像整张发票的资源,而您只想进行少量更改,则可以使用带有描述更改的补丁文档格式的HTTP 补丁。一旦你掌握了你的陈述,决定支持更多的“编辑”替代方案是完全合理的。

要认识到的关键点是,在 HTTP 中我们正在操作“文档”。 PUT,还有PATCH,是要求服务器使服务器的文档副本看起来像您的本地副本的消息。 POST类似(“请服务器先生,更改您的此文档的副本”),但对于实际更改的内容要少得多。

的更改是对文档的更改的副作用,对域的更改可能反过来导致其他文档的更改。

但关键思想是在具有统一消息接口的文档存储抽象之上构建域应用协议。

如何处理插入和更新的混合,即用户插入新数据并更改现有数据?我应该使用哪种 HTTP 方法?这会是什么样子?

如果您正在使用远程创作语义(使您的副本看起来像我的副本),那么您可以对文档的本地表示进行所需的任何编辑,然后(a)您的文档表示的完整副本PUT或者(b) 计算您所做更改的补丁文档表示,并使用补丁文档的表示进行 PATCH。

查看RFC 6901 JSON-Patch可能会有所帮助,它描述了您可能对 JSON 文档进行的不同编辑的标准语义。

如果您不使用远程创作语义,请使用 POST。例如,大多数万维网使用 HTML 表单和application/x-www-form-urlencoded表示形式将更改传达给服务器。

POST是具有最宽容语义的 HTTP 方法;它不保证是安全的或幂等的,结果只能在非常精确的条件下缓存,除了成功的 POST 使先前缓存的目标资源表示无效之外,实际上不允许中间组件做出任何假设。


推荐阅读