Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. 架构方案
  3. 正文
                           

【转载】系统设计 | RESTful API 使用问题和建议

2024-02-29 1443点热度 0人点赞 0条评论

系统设计 | RESTful API 使用问题和建议


虽然 RESTful API 已经成为业界对于 API 的共识,但是不得不说,但是不得不说它具有很多局限性。

RESTful API 和很多的技术流传的原因类似:始于一种非常理想化的愿景,但是在落地时却需要做出权衡和取舍。

它的流行开始于 Roy Fielding 的演讲,Roy Fielding 也是 HTTP 协议标准作者之一。

我猜测 Roy Fielding 的想法是,HTTP 协议已经是一个完善的应用层协议了,对于应用开发来说,只需要将所有的网络数据抽象为 URI 资源,然后配合可选的 Method 以及状态码就够用了。

但是,在我们实际落地的情况中,HTTP 的表达力完全不够。其限制主要有这几个:

  • 不是所有的信息都能抽象为静态资源,总会有一些动态行为存在。例如 Github 的 Star 操作,即使抽象为 star-records 也违反人的表达认知。

  • HTTP Method 无法在应用层随意拓展,很多行为难以被表达为合适的 Method

  • HTTP Status 无法在应用层拓展,很多业务状态无法使用 HTTP Status 表达

基于这些原因,人们为了遵守 RESFul 不得不绞尽脑汁,而如果使用 RPC 风格的 API 只需要使用合适的动词作为 URI 以及合适的 Payload 报文格式即可。

但是,总体来说,在一定程度上,使用 RESTful 风格,可以做到自解释性,减少了文档的依赖。而它的缺点,也可以通过一些团队规约避免。

本文基于技术研讨会讨论的内容,整理了一些使用 RESTful 的一些建议,包括如何通过团队契约在一定程度上弥补表达力不够的问题。

01 使用版本号解决版本不兼容问题

版本化 API 会有很多好处,而版本化 API 有很多种风格,包括:

  • 使用 URL 前缀

  • 使用 URL 后缀

  • 使用 Header 传参

  • 使用 Query 参数

推荐使用 URL 前缀实现,这样对后面 API 定义无侵入性且能通过 URL 表达版本。

实例:GET /v1/products/{id}

02 资源路径参考领域模型

在一般情况下,可以以模块、聚合根作为路径前缀,让路径排列更有规律。

参考类似模式: /[模块]/[版本号]/[聚合根]/{id}/[实体]/{id}/[属性/动作]

  • 在微服务项目中,模块部分可以是服务名

  • 资源参考领域模型设计用词,因此需要使用名词复数

  • 为了弥补 RESTful 不足,允许在路径结尾使用属性或者动作满足特定业务需求

  • 一般 URL 路径和文件名风格类似,使用中横线(Dash)

  • 和领域模型的层次类似,URL 层次不要太深,通过拆分小聚合实现短 URL

03 谨慎选择 HTTP Method

HTTP 协议提供了很多的 Method,但是处于团队理解成本的原因建议只使用下面几个 HTTP Method:

  • GET 查询

  • POST 新增

  • PUT 修改/更新

  • DELETE 删除

避免使用 PATCH,原因是无法表达业务含义,往往产生破坏性变更。例如,将保存的出库单提交,业务上需要修改单据的状态。应该避免使用 Patch 部分更新单据,建议使用 PUT /xxx/submit 的形式设计,让团队更容易理解,保证业务一致性。

04 实体的单复数应具有实际意义

通过 URL 应该能识别出返回的结构类型是否是一个列表或者分页的包装对象。

例如,通过 GET /v1/orders/{id}能猜测出返回结果是一个资源对象。

而通过 GET /v1/orders 能猜测出其结果是一个列表。

在某些项目中,复数词汇的 URL 默认返回分页对象,而一些项目会给分页 URL 添加一个 page 后缀,例如 GET /v1/orders/page。

05 合理实现幂等性

幂等含义:相同输入,应得到相同返回。

  • GET 天然具有幂等性

  • PUT DELETE Method 应设计为具有幂等性

  • POST 根据实际情况进行幂等性设计,例如在消息体中要求调用方传入事务 ID 实现幂等

06 提前设计查询语言或关键字

如果需要实现复杂的查询,可以提前设计一套基于 Query 参数的查询规则,尽可能实现通用的数据库字段查询。

需要考虑:

  • 分页

  • 排序

  • 关键字搜索

  • 与过滤条件

  • 并过滤条件

  • 开闭区间查询

这类实现一般需要结合具体数据库查询框架,例如 JPA、QueryDsl、Mybatis Plus 的 Wrapper 查询能力。

例如下面一个基于 Mybatis Plus 的通用 QueryWrapper:

// 查询条件的父类
public class QueryCondition {
    private String name;
    private Integer age;
}

// 为通用字段生成查询 Wrapper
public class MyMapper<T> extends BaseMapper<T> {
    public List<T> queryByCondition(QueryCondition condition) {
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        LambdaQueryWrapper<T> lambdaQueryWrapper = queryWrapper.lambda();

        // 根据条件动态构建查询
        if (condition.getName() != null) {
            lambdaQueryWrapper.like(T::getName, condition.getName());
        }

        if (condition.getAge() != null) {
            lambdaQueryWrapper.eq(T::getAge, condition.getAge());
        }

        // 执行查询
        return selectList(queryWrapper);
    }
}

 

07 状态码的选用

状态码的选用只用于前端处理一些通用的错误,而具体的业务规则错误统一使用 409(业务规则冲突)来返回,并返回约定的错误码、报错信息。

参考如下:

  • 200 请求成功

  • 201 创建成功

  • 400 数据校验失败

  • 401 用户未认证

  • 403 权限检查失败

  • 404 资源找不到

  • 405 不支持的 Method

  • 409 业务规则冲突

  • 415 不支持的数据请求格式

  • 500 服务器内部错误

  • 503 BFF 转发后端错误

08 批量处理接口

批量处理也是 RESTful 风格 API 不好处理的地方。有一些常见的做法:

  1. 由于 URL 使用复数已经代表资源,因此需要增加 /batch 后缀作为 URL 区分

  2. 使用类似版本号的处理方式在 URL 前增加 /batch 前缀

  3. URL 上可以不区分,在传入参数的格式上区分。例如,通过传入一个列表表达批量创建用户。

方案 2 看似更符合 RESTful API 风格,实际上非常容易让人困惑,因为无法通过 URL 语义区分批量接口;方案 2 的问题是,批量接口并不常见,使用前缀会影响很多接口的语义。

在一些文章中,还会区分 bulk 和 batch 的区别,认为 bulk 是对多个资源处理同样的操作,而 batch 是针对多个资源处理不同的操作。

当然在实践中我们不用如此区分,但在微服务环境下一些基础服务往往需要提供批量接口,避免循环调用带来的性能问题。

一些分页接口的例子:

  • 批量创建订单 POST /v1/orders/batch

  • 批量提交订单 POST /v1/orders/batch-submit

09 动词名词化技巧和场景

有些情况下动词 API 在充分建模的情况下也可以名词化。

比如登录的接口,我们在建模充分后识别到登录行为本质上创建了一个会话或者凭证,于是可以将:

POST /v1/users/login

改写为:

POST /v1/authorizes

另外一个例子,在金融领域需要对资产进行估值,看似应该使用:

POST /v1/assets/calculate

其实应该改写为:

POST /v1/capital-rating-transactions

每次评估后都会产生一次事务 ID。

10 区分成功和错误的返回结果

当 API 报错时,有些做法是使用一个容器包装错误信息和成功的消息体。

例如:

{  "code": "xxx"  "data": {}  "error": {}}

这种做法相当于再次封装了 payload 会显得有些冗余,主流的 API 设计一般是:当返回 HTTP 代码为 2xx 时,返回成功的对象,当使用其他代码时返回错误的对象。

错误的对象一般包含:

  • 业务意义的错误码

  • 错误消息

  • 错误的详细对象,用于某些特殊报错需要返回更多信息

11 创建和更新只返回必要信息

在讨论中,有一个话题是创建和更新操作是否需要返回处理后的对象?

返回的好处是,在编写 API 测试时方便取数,另外也可以复用详情的返回模型。

不返回的好处时,可以简化开发工作,仅仅返回必要的字段和数据,前端需要获取详细数据,可以调用查看相关接口。

同时,不返回全量信息也可以在部分场景提高性能,避免组装、传输过多数据。

12 可以参考的 API 规范和示例

除了前面的建议外,我们也经常参考一些真实的 API 设计规范,下面是一些常见可参考模仿的 API 设计示例:

  • https://docs.github.com/en/rest?apiVersion=2022-11-28

  • https://jsonapi.org/

  • https://wiki.onap.org/display/DW/RESTful+API+Design+Specification

参考资料

  • https://en.wikipedia.org/wiki/Representational_state_transfer

  • https://www.codementor.io/blog/batch-endpoints-6olbjay1hd

  • https://medium.com/paypal-tech/batch-an-api-to-bundle-multiple-paypal-rest-operations-6af6006e002


文 | 少个分号 (转载请注明出处)

关注公众号:DDD和微服务

本文仅供学习!所有权归属原作者。侵删!文章来源: DDD和微服务 -少个分号 :http://mp.weixin.qq.com/s?__biz=MzA4Mzc2MzcyMQ==&mid=2247484590&idx=1&sn=83390073ad0c26633e542ee9b60dafdd&chksm=9ff033c8a887baded8151545176906160bed25040e9a667739fe63af342999309199c0a9e58a&scene=21#wechat_redirect

更多文章:

  1. FSM-COLA无状态状态机
  2. 系统设计入门:成为高级软件工程师的指南
  3. 系统设计 | 哪些技术标准可以帮助系统设计?
  4. 用图讲解SOLID设计原则
  5. Spring事务无法生效的11个场景
  6. 殷浩详解DDD 第四讲:领域层设计规范
  7. 聊聊spring事务失效的12种场景,太坑了
  8. 系统设计 | 如何表达迭代技术方案?(战术篇)
  9. 系统设计 | 企业应用数据交换
  10. 设计模式在外卖营销业务中的实践
标签: 转载 HTTP 系统设计 API 架构方案 RESTful
最后更新:2024-02-29

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
  • 01 使用版本号解决版本不兼容问题
  • 02 资源路径参考领域模型
  • 03 谨慎选择 HTTP Method
  • 04 实体的单复数应具有实际意义
  • 05 合理实现幂等性
  • 06 提前设计查询语言或关键字
  • 07 状态码的选用
  • 08 批量处理接口
  • 09 动词名词化技巧和场景
  • 10 区分成功和错误的返回结果
  • 11 创建和更新只返回必要信息
  • 12 可以参考的 API 规范和示例
  • 参考资料
最新 热点 推荐
最新 热点 推荐
干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议 视频笔记:什么是微服务,为什么是微服务? 视频笔记:什么是AI 智能体? 视频笔记:什么是Flink? 如何秒级实现接口间“幂等”补偿:一款轻量级仿幂等数据校正处理辅助工具
Elasticsearch 使用误区之六——富文本内容写入前不清洗基于 MySQL Binlog 实现可配置的异构数据同步马蜂窝消息总线——面向业务的消息服务设计干货 | 论Elasticsearch数据建模的重要性你可以不用RxJava,但必须得领悟它的思想!如何秒级实现接口间“幂等”补偿:一款轻量级仿幂等数据校正处理辅助工具视频笔记:什么是Flink?视频笔记:什么是AI 智能体?
Eureka源码剖析之一:初始化-启动 RocketMQ 很慢?引出了一个未解之谜 浅谈SQL优化小技巧 定时任务原理方案综述 系统设计 | 如何表达迭代技术方案?(战术篇) Spring事务的七种传播特性 从滴滴的故障我们能学到什么 JVM GC配置指南

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (2) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (18) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2

x