核心概念
幂等性与重放安全
Maple 如何在不使用幂等键的情况下保证重试安全:重放安全的决策、按差异应用的菜单、幂等重放,以及您这一侧的去重。
网络可能在请求中途失败。关键问题是:如果您重试,是否会有事情发生两次?在 Maple 中,您最常重试的写入操作都被设计为可安全重复,因此遇到超时,您无需先核对状态就可以直接重试。
订单决策是重放安全的
重复发送订单决策(accept、deny、ready、complete、cancel 或 status)会返回相同的确认,不会重复产生副作用:
{ "object": "order_decision", "order_id": "ord_...", "decision": "accept", "status": "received" }如果您发送了 accept 但响应超时,请再发送一次。订单只会被接受一次,无论哪种情况您都会收到 { "status": "received" }。这样,一个简单的“遇到暂时性错误就重试”的循环就是正确的,您这一侧无需做任何额外记录。
发布菜单是一次差异应用
菜单发布描述的是整份菜单的目标状态,并以您的 externalId 为键,按与当前已发布内容的差异来应用:
- 重新发布完全相同的文档不会产生任何变化。
- 对象标识通过
externalId在多次发布之间保持稳定,因此重新发布不会重建或频繁变动对象。 - 校验是全有或全无:返回
400时不会更改任何内容,因此发布失败后菜单保持原样。
所以,如果发布超时,重新发送同一份文档是安全的。请参阅发布菜单。
预订写入携带 idempotency_key
创建、更改或取消预订(POST /v1/bookings、POST /v1/bookings/{id}/change、POST /v1/bookings/{id}/cancel),以场所身份取消预订(POST /v1/bookings/{id}/venue_cancel),以及为散客安排入座(POST /v1/locations/{locationId}/walk_ins),都需要在请求体中提供 idempotency_key,其范围限定在您的应用内。与候位加入一样,散客入座的键属于您的应用,在其所有门店间通用。键必须包含 1–255 个字符。对于 Maple 原生预订,事件记录会在同一个事务中记录该写入及其重放结果:
- 使用相同的键和相同的请求重试,会重放最初的结果:您会得到同一个
booking,不会重复预订、改期或取消。 - 相同的键搭配不同的请求(不同的客人、时段或备注)会返回
409,并带有code: "idempotency_key_reused"。请为每次要执行的写入生成新的键;UUID 即可。
重试会返回同一个预订标识,而不会重新应用变更;响应是从当前预订中读回的。键自首次占用起有 24 小时的重放窗口。重放会先于 expected_version 被检查,因为在原始写入成功后,expected_version 往往已经过期。
原生和第三方服务商的保留(hold)都会被持久化,并归获取它们的应用所有。重复提交同一个 slot_token 会重放该应用仍有效的保留,而不会再占用新的锁。其他应用无法使用或释放它,即使在共同授权的门店也是如此。未知的服务商保留在经过保守的 24 小时不活动隔离期后,会释放其配额预留,这与其保留的审计记录无关;已过期的操作无法通过重试恢复。
由第三方服务商支持的写入
对于 OpenTable,Maple 会在向服务商发起 I/O 之前,持久地占用该键和请求摘要,并使用同一个稳定的服务商请求 ID。待处理的更改或取消还会为镜像预订预留,以防其他合作伙伴写入。成功后,Maple 会同时记录镜像和重放结果。暂时性的镜像失败会同步重试;后台对账程序会修复未完成的工作。
服务商超时会在结果尚未确定时返回 409 operation_in_progress。请在 details.retry_after 秒后,使用相同的请求和键重试。在使用原始服务商请求 ID 重复执行预订写入之前,Maple 会先执行一次对账读取。已完成的写入会直接重放,不会再次调用服务商。
预订配置则改用内容寻址的发布方式:相同的配置无需显式的键,也不会产生任何变化。状态类场所操作(confirm、decline、seat、complete、no-show)使用 expected_version,而不是幂等键;在得到不明确的响应后,请先读取该预订,再执行其他操作。场所侧取消和散客入座则需要 idempotency_key,与其他键一样,范围限定在您的应用内。
{ "hold_id": "bkh_…", "guest": { "first_name": "Alex", "last_name": "Quinn", "phone": "+12125551234" }, "idempotency_key": "6f1c…" }重放事件是幂等的
POST /v1/webhook_events/{eventId}/replay 只会向尚未收到该事件的订阅重新投递。已经收到的订阅会被跳过,因此在故障恢复后重放,不会向正常的端点重复投递。
您这一侧:对投递去重
您必须自行添加幂等性的唯一地方是 webhook 处理程序。投递保证至少一次,因此同一个事件可能多次到达。请根据信封中的 id 去重:
async function handleEvent(event: { id: string; type: string; data: unknown }) { if (await alreadyProcessed(event.id)) return; // no-op on a repeat await process(event); await markProcessed(event.id);}尽可能让处理工作本身也具备幂等性,例如以 Maple 的 order_id 作为您自己订单记录的键,这样重复的 order.notification 会更新记录,而不是产生重复。不要依赖事件顺序;当顺序很重要时,请通过 GET /v1/orders/{orderId} 进行核对。请参阅Webhooks。
总结
| 操作 | 可以安全重试吗? | 原因 |
|---|---|---|
| 订单决策 | 是 | 重放安全;重复发送会返回 received,不会产生重复影响 |
| 菜单发布 | 是 | 按差异应用;重新发布相同内容不会产生变化 |
| 事件重放 | 是 | 跳过已投递的订阅 |
| 原生预订创建 / 更改 / 取消 | 是,需使用您的 idempotency_key |
相同的键 + 相同的请求体会重放;相同的键 + 不同的请求体返回 409 idempotency_key_reused |
| 散客入座和场所侧取消 | 是,需使用您的 idempotency_key |
相同的键 + 相同的请求体会重放同一个预订,即使其桌位已被占用;相同的键 + 不同的请求体(对于散客入座,还包括另一个门店)返回 409 idempotency_key_reused |
| 原生预订保留 | 在原保留仍有效期间 | 再次提交其 slot_token 会返回仍有效的保留 |
| 第三方服务商的预订写入 / 保留 | 使用相同的键 / 报价 | 持久占用及稳定的服务商请求 ID;未解决的结果可能需要对账 |
| 预订配置 | 是 | 相同的配置是内容寻址的空操作 |
| 接收 webhook | 由您去重 | 至少一次投递,请根据事件 id 去重 |