好几年前我参与过一次内部系统的联调,那边的接口文档让人印象深刻——/getUserList、/updateOrderStatus、/deleteGoodsById,动词和名词混着来,前端同学对接时全靠翻文档里的“方言对照表”才能猜出意图。后来我慢慢意识到,很多团队不是不想把API设计好,而是没人告诉他们“好”的标准长什么样。这大概也是“RESTful风格的API设计”这个话题经久不衰的原因:它不高深,但真正做到位的团队比例确实不高。这篇文章就围绕RESTful风格API设计这个核心,把资源建模、URL命名、HTTP方法语义、状态码规范这些关键点一次讲透。适合正在写接口、需要对接前后端、或者准备从零搭建API体系的开发者参考,看完至少能避开我在项目中踩过的大部分坑。
1. 先搞懂REST在解决什么问题
1.1 REST到底是什么
REST全称是Representational State Transfer,翻译过来叫“表述性状态转移”。这个词乍看特别劝退,我第一次接触的时候也花了不少时间才绕明白。换个角度理解,它其实是在描述一种交互方式:客户端通过HTTP协议,把一个又一个“资源状态”在客户端和服务器之间进行“转移”。
打个比方。你去餐厅吃饭,不用知道厨房的锅怎么颠、火怎么开,你只需要看菜单(资源清单),告诉服务员“来一份宫保鸡丁”(提交一次状态变更),厨房炒好之后由服务员端上来(返回状态结果)。整个过程里,菜单是统一接口,点餐动作是HTTP方法,菜品就是资源。REST的思想本质上就是这么朴素——把系统里的东西都抽象成资源,然后用一套统一的动作去操作它们。
那为什么叫“表述性”而不是“表示性”?因为客户端拿到的其实不是资源本身,而是它在某一时刻的“表述”。比如一个用户资源,在JSON里就是一个对象,在HTML里可能是表格的一行,在XML里又是另一种写法。服务器返回给客户端的,永远是某种“表述”,而不是数据库里的原始二进制结构。正是这个细节奠定了REST的一个核心理念:客户端与服务器通过“表述”交互,彼此之间不需要知道对方的内部实现。
从实践角度看,把资源当作名词来理解,是RESTful设计里最关键的一步认知转换。很多人设计中卡壳,往往就是因为脑海里还是“我要设计一些接口”,而不是“我要定义一组资源以及它们支持的操作”。这两种出发点,最终产出的接口形态会差别很大。带着资源思维去做设计,URL自然而然就是/books、/orders这样的名词路径,而不是/getBooks、/createOrder这种动作路径。
1.2 六大约束与统一接口
很多人以为REST只是“URL写得好看点”,其实真正的REST是有理论出处的。一位研究者在自己的博士论文里提出了REST架构风格,并给出了六个约束条件。实际工程中并不是每条都被严格兑现,但它们共同构成了RESTful设计的“宪法”:
- 客户端-服务器分离:前端和后端各干各的,前端不关心后端存储引擎,后端不关心用户界面渲染,两者通过接口交互。
- 无状态:服务器不在自己的内存里保存客户端上下文,每次请求都携带足够信息(比如令牌、分页参数),服务器独立处理。
- 可缓存:响应可以标明是否可缓存以及缓存策略(配合Cache-Control、ETag),减少重复请求,降低服务器压力。
- 统一接口:这是最核心的一条,强调通过标准方法操作资源、使用标准状态码表达结果、用媒体类型描述交互内容。
- 分层系统:客户端通常不需要知道它连的是代理、网关还是原始服务器,每一层只管自己的职责,中间层可以透明加缓存、做负载均衡。
- 按需代码(可选):服务器可以把代码(如脚本)发给客户端执行,但这个约束在实践里用得很少。
在这六个约束里,最容易被低估的是“统一接口”。它又拆成四件事:资源通过URI来识别;客户端通过资源的表述来操作资源;消息是自描述的,自带Content-Type、Cache-Control等元信息;超媒体驱动应用状态(也就是常说的HATEOAS)。
举个例子更容易理解。当你查询一个订单时,如果订单状态是“待支付”,RESTful的“超媒体驱动”倾向会在响应里返回一个支付链接:
{ "order_id": "order_1001", "status": "pending", "links": { "pay": "/v1/orders/order_1001/payment", "cancel": "/v1/orders/order_1001" } }客户端不用自己拼URL、不用写一堆状态判断逻辑,照着返回的链接走就行。虽然很多团队觉得HATEOAS成本高、收益不明显,但在面向第三方开放API的场景里,优势非常突出——客户端永远不会硬编码一个拼接出来的URL,服务端调整路径也不会连带打挂所有调用方。
1.3 为什么RESTful设计有价值
这不是一个理论空谈的问题。我用一个场景说明:假如你要给订单模块设计接口,如果放任团队成员各写各的,有人用/order/create,有人用/orders/createOrder,还有人干脆写/api/order.php?action=add,前端接起来就是一场灾难。RESTful给了一个共同的语言框架:资源用名词表达,动作交给HTTP方法。接口一看就能猜出语义,不需要翻文档确认。
另外,RESTful天然贴合HTTP生态。浏览器缓存、网关限流、CDN加速、日志采集,这些基础设施全都认标准的方法和状态码。接口设计得越标准,你能免费享受的生态红利就越多。反过来,如果设计成/doSomething?cmd=xxx这种RPC风格,很多通用工具和中间件就使不上劲了,能省的心力都变成要自己造轮子的成本。
2. 设计RESTful API的四个关键战场
2.1 资源建模与URL命名
资源建模是一切设计的起点。拿到需求第一步不是想URL,而是先列出“系统里有哪些核心实体”。比如图书管理系统的核心资源是“图书”和“作者”,订单系统的核心资源是“订单”和“用户”。资源定下来之后,URL设计就有章可循了。
我的经验可以浓缩成这几条:
- URL里只放名词,不放动词。
/books而不是/getBooks。 - 推荐用复数名词表示资源集合。
/books/{id}取单本图书,/books操作整个图书集合。 - 用嵌套表达资源隶属关系,但嵌套层次控制在两层以内。
/authors/{authorId}/books可以接受,但/users/{userId}/orders/{orderId}/items/{itemId}这种就不要了。 - 查询参数负责过滤、排序、分页,不属于URL路径的一部分。比如
/books?category=tech&sort=created_at&page=2&page_size=20。
这里要克制一个冲动:为了“好懂”把动作写进URL。/books/publish看着挺直白,但“发布一本书”本质上是把图书状态从“草稿”改成“已发布”。用资源来表达,应该是PATCH /books/{id}带上{"status":"published"}。两种做法差别不只是风格,前者会导致URL爆炸——每多一个动作就多一个接口;后者始终只有几组资源接口,扩展性完全不一样。
再对照一张表,看看常见的反模式:
| 不推荐 | 推荐 | 理由 |
|---|---|---|
| GET /getBooks | GET /books | 动作交给HTTP方法表达 |
| POST /updateBook | PUT /books/{bookId} | 更新语义清晰 |
| DELETE /deleteBookById/123 | DELETE /books/123 | 路径更短更统一 |
| GET /books/page/2 | GET /books?page=2 | 分页属于查询参数 |
2.2 HTTP方法语义与幂等性
HTTP自带一套动作词汇表,RESTful设计就是让它们各司其职:
- GET:查询资源,不改变服务器状态,幂等。
- POST:创建资源,不要求幂等。
- PUT:全量更新资源,幂等。
- PATCH:部分更新资源,不保证幂等。
- DELETE:删除资源,幂等。
“幂等”这个词是初学者最容易懵的点。我当年的理解方式是:同一个请求不管发一次、两次还是十次,服务器的最终结果都一样,就叫幂等。GET和DELETE天然幂等,PUT把资源整体替换为指定内容也幂等,但POST创建一个新订单就完全不幂等——发两次可就生成两单了。实际开发里,POST接口的幂等性问题常常靠“幂等键”解决:客户端生成一个唯一的Idempotency-Key,服务器记录处理过的键,重复请求直接返回第一次的结果。这个细节我后面专门展开讲。
方法选用的坑也很常见。有人想更新资源,用POST对着/updateBook发请求;想删除资源,用POST带{action:'delete'}。这套做法在RPC时代很正常,但在RESTful风格下属于把语义揉碎了。建议先记住一句话:能用HTTP方法表达的语义,不要自己在URL里再造一套。刚开始不习惯,忍着多写几次,后面就会顺手。
2.3 状态码规范与错误处理
状态码是HTTP自带的“情绪表达器”。合理使用状态码,能让调用方不解析响应体也能大概知道结果类别:
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 200 | OK | 查询与更新成功 |
| 201 | Created | 创建成功 |
| 204 | No Content | 删除成功、无响应体 |
| 400 | Bad Request | 参数不合法 |
| 401 | Unauthorized | 未认证或凭证失效 |
| 403 | Forbidden | 已认证但无权访问 |
| 404 | Not Found | 资源不存在 |
| 409 | Conflict | 状态冲突,如库存不足、名称重复 |
| 422 | Unprocessable Entity | 语义校验不通过 |
| 429 | Too Many Requests | 请求频率过高 |
| 500 | Internal Server Error | 服务器未预期错误 |
| 503 | Service Unavailable | 服务维护或过载 |
我见过最普遍的状态码滥用,就是无论什么错误都返回200,然后在业务码里告诉前端“这次失败了”。这样做有几个坏处:监控系统统计错误率跟不上、网关做故障重试看不到5xx、前端写业务逻辑还得先拆业务码。正确的做法是:HTTP状态码表达“结果类别”,响应体里放详细错误信息,两者各司其职。
错误响应体我个人习惯统一成这种结构:
{ "code": "BOOK_NOT_FOUND", "message": "The book with id 12345 does not exist.", "detail": { "resource": "book", "id": "12345" } }code给程序判断用,message给人看,detail带上下文信息。保持这个结构固定,客户端就能写一套统一的错误处理逻辑,不用为每个接口单独适配。还有一个很重要的习惯:错误信息要能直接帮人定位问题。简单一句error: failed跟没写差不多,至少要带上“哪个资源”“什么条件”“期望是什么”这三类信息。
2.4 数据格式、版本与认证
数据格式上,JSON已经是事实标准。但有几个细节值得全团队统一约定:一是字段命名,建议全项目统一成snake_case或者camelCase,不要混用。二是时间格式,强烈建议用ISO 8601字符串,比如2025-01-15T10:30:00Z,客户端拿到之后自行转换时区,避免传Unix时间戳还需要到处解释。三是空值表达,建议明确是输出null、空数组还是直接省略字段,前端对这些语义差别非常敏感。
版本管理是另一个绕不开的话题。对外API一旦上线,改字段名、删接口都可能影响调用方。最常见的做法是在URL里放版本号:/v1/books、/v2/books,新旧版本可以同时存在,给客户端迁移留出时间。内部项目可以约定“无版本约束,破坏性变更直接改”,但只要是开放平台,版本策略必须提前定好,不然上线第一天就要为兼容性焦头烂额。
认证方面最常用的方案是JWT(JSON Web Token)。客户端登录后拿到token,后续请求在Authorization: Bearer <token>里携带它。RESTful风格下身份信息属于请求上下文的一部分,配合无状态约束,服务端不需要保存会话,扩缩容会轻松很多。这里我给一个简单的选型方向:内部服务间通信可以用简单Token;面向用户的应用,Session方案稳定传统,JWT更适合水平扩展需求强的场景;如果要对接第三方开放平台,OAuth 2.0是标准答案。还有一点容易被忽略:HTTPS是必须的,否则token在链路上跟裸奔没什么区别。
3. 实操走一遍:图书管理系统API
3.1 从需求到资源模型
理论说得再多,不如直接走一遍落地流程。假设要给一个在线图书商城写后台接口,业务需求大致是:管理员维护图书信息,用户浏览图书、按分类筛选、查看详情、下单购买,订单创建后需要更新状态。
拿到这些需求,我不会急着写接口,而是先把资源画出来:
- book:图书,字段有id、标题、ISBN、作者、分类、价格、库存、状态(上架/下架)、创建时间。
- author:作者,可以单独管理,也可以作为图书的关联资源。
- user:用户,登录账号信息。
- order:订单,字段有id、用户id、下单时间、总额、状态(待支付/已支付/已发货/完成/取消)。
- order_item:订单明细,一条订单可以包含多本图书。
资源模型定下来之后,再看资源之间的关联。图书和作者是多对多,用户和订单是一对多,订单和订单明细是一对多。这类“集合 + 子资源”的结构,恰好在RESTful里表达能力最强,接口一眼就能看明白隶属关系。
3.2 完整的接口设计表
基于上面的资源模型,我会整理出一张接口设计表。这张表也是我后续写接口文档的蓝本:
| 方法 | 路径 | 功能描述 | 成功状态码 |
|---|---|---|---|
| GET | /v1/books | 分页获取图书列表,支持过滤排序 | 200 |
| GET | /v1/books/{bookId} | 获取图书详情 | 200 / 404 |
| POST | /v1/books | 新建图书 | 201 |
| PUT | /v1/books/{bookId} | 全量更新图书信息 | 200 / 404 |
| PATCH | /v1/books/{bookId} | 部分更新(如上下架) | 200 / 404 |
| DELETE | /v1/books/{bookId} | 删除图书(软删除) | 204 / 404 |
| GET | /v1/authors/{authorId}/books | 获取某作者的全部图书 | 200 |
| GET | /v1/users/{userId}/orders | 获取某用户的订单列表 | 200 |
| POST | /v1/users/{userId}/orders | 为用户创建新订单 | 201 |
| GET | /v1/orders/{orderId} | 获取订单详情 | 200 / 404 |
| PATCH | /v1/orders/{orderId} | 更新订单状态 | 200 / 404 |
| POST | /v1/auth/login | 登录并获取token | 200 |
| POST | /v1/auth/refresh | 刷新token | 200 |
注意几个细节:订单状态变更我用PATCH而不是PUT,因为通常只改一个字段,不需要把整个订单对象传一遍;删除图书用软删除,在数据库里标记删除时间但保留记录,历史订单的关联数据不会因此断裂;登录、刷新这类接口本质上不是资源操作,但因为它们围绕“身份”工作,我把它归到auth命名空间下,这是工程上常见的折中做法。
3.3 请求响应实例演示
下面看几个关键接口的实际交互过程。
分页获取图书列表,带上过滤和排序参数:
curl -X GET "https://api.example.com/v1/books?category=tech&page=2&page_size=10&sort=-create_time" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."这里我用了sort=-create_time,负号表示降序,这个约定比传sort=desc更紧凑。分页参数page和page_size要提前定义好上限,比如page_size最大100,避免有人一次拉一万条把服务打满。
服务端返回:
{ "items": [ { "id": "book_1001", "title": "深入理解计算机系统", "isbn": "9787111544937", "author": { "id": "author_21", "name": "某位作者" }, "category": "tech", "price": 79.00, "stock": 120, "status": "published", "created_at": "2025-02-01T10:00:00Z" } ], "pagination": { "page": 2, "page_size": 10, "total": 183, "total_pages": 19 } }分页信息我倾向于放在顶层pagination字段,而不是塞到每一个item里。这样前端可以独立处理分页状态,不用从列表数据里反推。
创建订单的请求示例:
curl -X POST "https://api.example.com/v1/users/user_88/orders" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: uuid-1234-5678" \ -d '{ "items": [ { "book_id": "book_1001", "quantity": 2 }, { "book_id": "book_1002", "quantity": 1 } ], "address": { "receiver": "某用户", "phone": "13800138000" } }'创建订单是典型的非幂等操作,我在请求头里带了Idempotency-Key,服务端用这个键去重。客户端断网重试时,同一个键不会创建出两个订单,这个细节在支付场景里尤其值钱。
如果库存不足,接口返回409:
{ "code": "INSUFFICIENT_STOCK", "message": "Book book_1002 only has 0 items in stock.", "detail": { "book_id": "book_1002", "requested_quantity": 1, "available_stock": 0 } }400和409的选择有时候会犹豫:库存不足算客户端错误,但语义上不是“格式不对”,而是“业务状态冲突”,所以用409比400更贴切。这种细节决定了调用方能不能快速定位问题,也很能体现设计者的基本功。
3.4 接口文档与团队协作
接口设计得再规范,如果没有一份能沉淀下来的文档,联调效率一样上不去。这里说一个我的实操经验:设计接口的时候就把文档写出来,而不是等代码写完再补。写文档的过程会迫使你审视设计,常常能提前发现URL、字段、参数上的矛盾。
文档工具方面,这几年主流选择是OpenAPI(也就是Swagger生态)那套方案,用一份YAML或JSON描述每个接口的路径、参数、响应结构。生成的文档页面能直接发送试请求,对前端同学很友好。团队规模小的时候用Postman的文档共享也够用,但接口数量上来之后,OpenAPI的机器可读性优势会越来越明显,各种自动化工具都能基于它做代码生成、契约测试。
还有个容易被忽略的点:字段变更要有变更记录。我见过不少项目,接口文档停留在第一版,代码迭代了好几轮,前端对着旧文档开发,联调的时候才炸锅。建议接口文档里标注last_updated字段,甚至维护一份简短的变更日志,能减少大量团队内耗。
4. 避坑指南与调试技巧
4.1 新手易踩的七个坑
第一,URL里动词化。/getUserInfo、/createOrder这种写法是直觉反应,但一旦写成这样,后面想统一管理资源就很难了。我的建议是一开始就写成GET /users/{id}、POST /orders,不顺手也要先忍一下。
第二,忽略HTTP语义。POST和GET分不清是入门级问题,进阶的问题是PUT、PATCH、DELETE混用。全量更新用PUT,部分更新用PATCH,不要把PUT当万能药,也不要为了避免纠结就全都用POST。
第三,状态码只认200和500。这是不少团队的缩影。状态码本身就是协议的一部分,能把404、409、422用对,前端写错误处理时会轻松非常多。
第四,嵌套层级过深。/schools/{schoolId}/classes/{classId}/students/{studentId}/grades这种路径看着完整,实际上维护成本极高。如果确实需要这种聚合数据,不如设计专门的查询端点,或者用查询参数展开。
第五,忽略分页和过滤的统一。有人用limit,有人用pageSize,有人从0开始翻页,有人从1开始。文档里不写清楚,前端每次对接都要确认一遍。建议团队定一个统一的翻页参数命名和起始规则,定了之后不要随便改。
第六,字段类型和命名飘忽。ID一会儿是数字一会儿是字符串,时间一会儿是时间戳一会儿是ISO字符串,前端解析逻辑写起来非常痛苦。数据格式的约定比接口数量更重要,一次性定好,后面少吵很多架。
第七,不休止地加“方便接口”。为了让前端少跳一次页面,后端加一个/getBookWithAuthorAndUser这种接口,一开始很爽,但最终会堆成一个没人能维护的接口垃圾场。前端能组合出来的数据,尽量让前端组合。
4.2 调试工具与排查思路
排查接口问题,我平时用得最多的是几个工具组合。
第一是curl。调试单个请求最快,特别是验证状态码和响应头:
curl -i -X PATCH "https://api.example.com/v1/books/book_1001" \ -H "Content-Type: application/json" \ -d '{"status":"off_shelf"}'-i打印响应头,能看到HTTP版本、状态码、返回的Header。排查缓存问题几乎离不开看响应头里的Cache-Control和ETag,这种底层信息图形工具反而不方便看全。
第二是Postman或Apifox这类图形工具。它们适合保存一组请求集合、做环境变量切换、写自动化测试脚本。Apifox把接口文档和调试放在同一界面,对团队协作效率提升很明显,尤其在接口数量多的时候,不需要在多个工具之间来回切换。
第三是抓包工具。排查前端发出的真实请求,浏览器开发者工具的Network面板就够用;想看得更细,可以放一个本地代理做转发,观察请求和响应的原始报文。
排查的思路可以总结成三步:先看状态码判断结果大类,再看响应体里的错误码定位业务逻辑,最后用日志或链路追踪确认服务器内部发生了什么。不要一上来就翻数据库日志,那样往往事倍功半。还有一个很实用的技巧:排查线上问题先看时间戳,把调用方的请求时间、网关日志时间、服务端日志时间拉齐,很多“偶现”问题其实是日志对不上时间轴造成的错觉。
4.3 RESTful与GraphQL、RPC怎么选
RESTful不是唯一的高质量API风格,工程上经常需要跟GraphQL、RPC做比较。
RPC风格把API设计成“函数调用”,比如orderService.createOrder(userId, items)。它很直观,但问题在于函数参数和返回结构跟具体语言绑定,跨语言调用时经常需要写一堆适配层。gRPC算是RPC的现代升级版,性能好、有严格的接口定义,但调试工具链比HTTP/REST重很多,对团队基础设施要求也更高。
GraphQL的核心卖点是“客户端按需取字段”,一次请求可以精确拿到自己要的数据,不会出现REST里常见的“返回一大堆用不到字段”的情况。但它引入了自己的查询语法,缓存复杂度也高,团队学习成本不小,而且没办法完全利用HTTP的缓存机制。
我个人的选型经验很朴素:
- 对外公开API、结构简单、追求通用兼容性,选RESTful,没有悬念。
- 内部服务间通信、性能敏感、接口数量大,可以考虑gRPC。
- 客户端类型多、数据需求差异大、需要灵活查询,考虑GraphQL。
- 大多数团队,先老老实实用RESTful把业务跑通,比追逐“更先进”的风格更靠谱。
从实际项目来看,RESTful的真正优势不是“技术最先进”,而是“生态最成熟、损耗最低”。对绝大多数后端开发者来说,第一门API设计课都该从RESTful学起,把HTTP的语义、状态码、资源建模这些基本功打牢,之后再看别的风格会容易得多。
5. 进阶话题:幂等、安全与可观测性
5.1 幂等键的实现细节
前面提到POST接口的幂等键,这里展开说一下实现层面的几个决策点。
幂等键一般放在请求头里,比如Idempotency-Key: uuid-1234-5678。服务端收到请求后,先查这个键有没有处理过,如果处理过就直接返回第一次的结果;没处理过就执行后续逻辑,完成后把键和响应存起来。
设计时有几个细节需要注意。第一,键的存储建议单独建一张表,字段至少包含键值、请求摘要、响应内容、创建时间。第二,幂等键要设置过期时间,常见做法是24小时,超过时间客户端需要生成新键。第三,服务端要做并发保护,两个同样键的请求同时到达时不要在业务代码上重复执行,加唯一索引或者分布式锁都可以。
真正坑人的点是:幂等键只对同一个业务操作有效。如果客户端拿创建订单的键去请求刷新token,服务端得能识别出来并拒绝。所以幂等键的校验一定要绑定接口路径和请求参数摘要,不能只查一个键存在与否。
5.2 可观测性体系
接口设计得好不好,运维阶段才能真正见分晓。这里说的可观测性,指的是日志、指标、链路追踪三件事。
日志方面,建议所有接口统一输出结构化日志,包含请求ID、用户ID、接口路径、方法、状态码、耗时。JSON格式最好,方便日志系统直接解析。说到请求ID,我强烈建议在网关层为每个请求生成一个traceId,传到后端各个服务,这样前端报一个错误,你拿着traceId就能把整个调用链的日志拉出来。
指标方面,三个数必监控:QPS、P95耗时、错误率。QPS看流量波动,P95耗时看用户体验,错误率看健康状态。配合告警,接口开始恶化时能第一时间发现,而不是等用户投诉。
还有一个小经验:给每个资源操作加上简单的业务埋点。比如“图书创建失败”“订单支付回调异常”,这类事件指标能帮你发现很多HTTP状态码看不出来的问题。
5.3 安全与限流补充
RESTful API的安全性有几个常规动作,缺一不可。
第一是限流。无论接口设计得多优雅,没有限流也会被打爆。常见的限流策略有固定窗口、滑动窗口、令牌桶。对公开接口,我建议基于用户维度做限流,比如每个用户每分钟最多100次请求;对高风险接口如登录,要更严格,防止暴力破解。
第二是敏感信息防护。响应体里不能返回密码哈希、手机号、身份证号这类数据。数据库字段脱敏输出是常识,但实际项目里经常因为“图省事”把整个实体序列化出去,造成严重的数据泄露风险。
第三是CORS策略。跨域请求要精确控制允许的来源域名,不要图方便用*。配合HTTPS和JWT短有效期,能挡住很大一部分基础攻击。CORS配置不对导致的问题不好排查,建议上线前用一个跨域测试页面完整走一遍。
再补充一个对实战很有用的理念:不要在URL里暴露不必要的信息。/v1/users/{userId}/orders这种设计,userId如果是自增数字,很容易被遍历抓取。用UUID或者带前缀的业务ID,攻击成本会高很多。这个点很多人等到出事才意识到,但设计阶段就能避开。
最后再分享一个我做接口评审时反复讲的原则:如果某一天你需要扩展接口,先想想是不是有新的资源出现了,而不是想着往旧接口里塞参数。资源模型清晰,接口的扩展性自然就好。这个原则,配合规范的状态码和统一的错误结构,基本就能撑起一个让前后端都省心的API体系。RESTful这条路踩过几次坑之后,我现在设计接口第一反应永远是“这个操作对应哪个资源、应该用哪个方法”,而不是“要不要加个专用接口”。把这种思维变成习惯,你会发现自己写出的接口越来越清爽。