RESTful API设计疑问:邮件营销活动关闭与订阅的请求方式选择
一、活动关闭接口设计
现有方案(推荐)
PATCH /campaigns/<campaignId>
{ "status": "CLOSED" }
这是完全符合REST规范的设计:
/campaigns/<campaignId>明确指向「营销活动」这个核心资源- PATCH方法的语义就是部分更新资源状态,完美匹配“仅修改活动状态为关闭”的需求
- 请求体清晰指定要变更的字段,语义直白无歧义
备选方案分析
POST /campaigns/<campaignId>/close
这种属于RPC风格设计,把操作动作(close)直接写入URL,违背了REST「资源导向」的核心原则——REST要求URL标识资源本身,而非具体操作行为。虽然POST也能实现功能,但不符合REST的设计理念,不推荐使用。替换为GET请求?
绝对不可行。HTTP规范明确要求GET请求是无副作用、幂等的,仅用于获取资源,不能触发任何状态变更。关闭活动会修改资源状态,属于有副作用的操作,用GET会导致浏览器缓存、重复请求等问题,严重违反HTTP语义。
二、用户订阅接口设计
错误方案:GET请求
GET /campaigns/<campaignId>/subscribe/<userId>
和活动关闭的GET请求问题一致:订阅操作会创建用户与活动的关联关系(属于状态变更),GET不允许用于这类有副作用的操作,直接排除。
较优方案:POST子资源
POST /campaigns/<campaignId>/subscriptions
{ "userId": <userId> }
这个设计更符合REST规范:
- 把「用户订阅」视为活动下的子资源集合(subscriptions),URL清晰指向该资源的归属
- POST方法的语义是创建新资源,刚好匹配“为活动新增一条用户订阅记录”的需求
- 请求体携带订阅所需的userId,语义明确
更贴合REST的扩展方案
如果订阅本身是独立的业务实体,可以直接设计为顶级资源:POST /subscriptions
{ "campaignId": <campaignId>, "userId": <userId> }
这种设计更符合REST“一切皆资源”的思想,后续如果需要查询、修改、取消订阅记录,能更自然地扩展API(比如GET /subscriptions/<subscriptionId>查看订阅详情、DELETE /subscriptions/<subscriptionId>取消订阅)。
内容的提问来源于stack exchange,提问作者Martial

