
大家好,我是Tony Bai。
欢迎来到我们的专栏 《API 设计之道:从设计模式到 Gin 工程化实现》的第三讲。
在前面两讲中,我们不仅统一了资源导向的命名规范,还用泛型封装了标准的 CRUD 控制器。一切看起来都很美好,直到有一天,产品经理走到了你的工位旁,提了一个需求:
"Tony,我们需要加一个'取消订单'的功能。取消时要校验订单状态,退回库存,还要给用户发短信。"
这时候,你还没从上一讲的 CRUD 思维中走出来,下意识地想:
"取消订单?不就是把订单状态改成 Cancelled 吗?这简单!"
于是你写出了这样的代码:
go
// PATCH /api/v1/orders/:id
{
"status": "cancelled"
}
你觉得这很 RESTful,很规范。但几天后,问题来了:
-
有的开发人员直接改了数据库状态,但忘了发短信。
-
有的在退库存时发生了错误,但订单状态却已经变更为取消了,导致数据不一致。
-
前端同学跑来问:"为什么我把状态改成
cancelled报错了?哦,原来只有pending状态才能取消啊,你不早说?"
其实,这里犯了一个典型的**"过度 CRUD 化"**错误。
并不是所有的业务逻辑都能(或者应该)被映射为字段的修改。对于那些副作用大、逻辑复杂、具有明确业务意图 的操作,我们需要引入一种新的设计模式:自定义方法(Custom Methods)。
今天这一讲,我们就来聊聊当 CRUD 不够用时,如何在 Gin 中优雅地设计"非标行为"。

为什么 PATCH 不是万能的?
在 API 设计中,有一条黄金法则:API 应当表达"意图",而非仅仅暴露"数据"。
使用 PATCH 更新状态字段,虽然符合 REST 的字面含义,但它掩盖了业务的复杂性。
-
副作用(Side Effects)隐藏 :
PATCH通常暗示着轻量级的数据字段更新。但"取消订单"可能触发一系列沉重的后端流程(退款、通知、库存释放)。将这些副作用隐藏在一个简单的字段更新背后,违背了"最小惊讶原则"。 -
状态机逻辑泄露 :订单的状态流转通常是有严格限制的(比如只能从
Pending->Cancelled)。如果使用PATCH,意味着客户端需要了解这些流转规则,否则就会收到各种不知所云的校验错误。 -
权限粒度难控制 :如果"修改收货地址"和"取消订单"都走
PATCH /orders/:id,你怎么在网关层做细粒度的权限控制?难道要解析 Body 内容吗?