做后端接口设计这些年,最折腾人的往往不是业务逻辑,而是接口风格从一开始就选错了方向。每次新项目启动,技术评审上几乎都会吵一轮:RPC 与 RESTful 到底选哪个。有人是 RESTful 原教旨主义者,觉得万物皆资源;有人则觉得 RPC 简单直接、性能好,RESTful 那套规则麻烦又低效。我的态度一直是不站队,看场景,看团队,看业务形态。
这篇文章把 RPC 和 RESTful 的底层逻辑、适用边界、设计细节、常见坑,以及两者如何在同一系统里和谐共存,整体梳理一遍。适合后端开发、架构师,或者是刚接触分布式系统、正在为接口风格纠结的团队。读完后你至少能回答一个问题:在下一个项目里,服务之间的接口应该长什么样。
1. 两种接口风格:一个面向函数,一个面向资源
1.1 RPC 的底层逻辑
RPC 全称远程过程调用,核心思路特别朴素:让客户端像调用本地方法一样调用远程服务。你在代码里写 orderService.getOrderById(id),框架替你完成网络传输、参数序列化、服务发现、超时重试这些脏活,调用方感知不到远程边界。
这种风格的关键词是"动词"和"方法"。接口设计天然围绕操作展开:下单、支付、退款、查询余额。方法名 + 参数列表 + 返回值,整个模型和本地函数调用几乎一致,程序员不需要切换思维模式。RPC 通常配合强类型接口定义(IDL)来约束两端契约,比如定义一个 GetUserRequest,里面有 user_id 字段,客户端和服务端都基于同一份契约生成代码,类型不匹配的问题在编译阶段就暴露了。
RPC 还有个不可忽视的优点是传输效率。很多主流 RPC 框架使用二进制序列化协议,数据体积比 JSON 小,序列化速度快,还支持多路复用。在服务间调用频率高、对延迟敏感的场景下,这个优势能直接反映到系统吞吐上。我自己实测过类似场景:同样一个查询接口,JSON over HTTP 和二进制 RPC 相比,响应体小了接近一半,P99 延迟能差出 20~40 毫秒。在链路长、调用多的时候,这个差距会被成倍放大。
但 RPC 的问题也很明显:跨语言调用必须依赖 IDL 和代码生成工具链,一旦语言种类变多,工具链维护成本就上来。而且 RPC 服务通常不天然适合被浏览器、小程序、第三方直接调用,因为它对调用者要求太高,文档、协议、安全策略都比 HTTP 接口复杂。典型的内网亲密调用风格,藏得越深越好。
1.2 RESTful 的底层逻辑
RESTful 不是一个协议,不是一份规范文档,而是一种架构风格。它的核心是把一切业务能力都抽象成"资源",通过 HTTP 方法表达对该资源的操作。用户是资源,订单是资源,文章是资源。查用户就是 GET /users/{id},创建用户就是 POST /users,更新用户就是 PUT /users/{id}。一切都是名词,操作被压缩到 HTTP 方法的语义里。
这套设计的妙处在于统一接口带来的低沟通成本。任何一个懂 HTTP 的人,不看文档也能猜出 GET /orders/{id} 大概是干嘛的。浏览器可以直接打开,Postman 可以直接调试,第三方开发者几乎零门槛接入。这也是为什么几乎所有开放平台、SaaS 服务的对外 API 都选择 RESTful 风格。
RESTful 还强调无状态和资源导向。无状态意味着服务端不保存客户端会话上下文,每个请求自包含,这天然适合横向扩容;资源导向意味着 URL 是稳定标识符,而不是一堆动词拼出来的"动作 URL"。比如 POST /orders 是下单,后续状态变成"已支付",你不需要再发明一个 POST /orders/pay,而是可以 PATCH /orders/{id} 或者用 POST /orders/{id}/pay 作为子资源动作。这个微妙的设计习惯,直接影响接口长期演进是否顺畅。
RESTful 的缺点也实实在在。面对高频、细粒度的内部调用,资源建模会非常别扭。比如内部服务调用方需要同时拿用户信息、用户最近订单、用户优惠券,REST 风格下要么拆成三次请求,要么硬造一个聚合资源接口,怎么设计都不够自然。而 RPC 只需要定义一个 GetUserWithOrdersAndCoupons 方法,一次调用搞定。
| 对比维度 | RPC | RESTful |
|---|---|---|
| 设计视角 | 面向方法/动词 | 面向资源/名词 |
| 通信协议 | 二进制协议居多,也可走 HTTP | 几乎都基于 HTTP/HTTPS |
| 接口契约 | 强类型 IDL,代码生成 | 通常靠文档和约定,OpenAPI 规范 |
| 性能表现 | 高,序列化快、体积小 | 一般,JSON 体积大,解析开销高 |
| 对外开放 | 较差,工具链要求高 | 优秀,浏览器/第三方直接可用 |
| 调试便利性 | 需要专门工具和代理 | 浏览器、curl、Postman 随处可用 |
| 适用边界 | 内部服务、高频调用、多语言微服务 | 开放 API、浏览器端、跨团队长生命周期接口 |
一句话总结两者的关系:RPC 是服务内部的高速公路,RESTful 是对外的标准化门面。它们不是敌人,而是服务不同入口的不同策略。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 选型困境:不是非此即彼,而是场景匹配
2.1 什么时候选 RPC:性能与开发效率优先
选 RPC 的核心判断标准是"这是不是团队内部的亲密调用"。典型场景包括:微服务架构中的服务间同步调用、实时推荐系统的高频查询、需要跨语言协作的多个后端服务、以及对接口响应时间比较敏感的业务链路。
我和很多团队交流过,普遍经验是:超过 90% 的 RPC 调用发生在机房内部网络,远小于一次公网请求的延迟。这个物理条件决定了内部接口不需要考虑网络环境恶劣、带宽受限、客户端兼容这些 RESTful 擅长解决的问题,反而更在意序列化开销、连接复用、服务发现和治理能力。
RPC 在开发效率上的优势也不止于性能。因为 IDL 是强契约,前后端语言不同也能自动生成客户端,调用方拿到的就是一个本地方法,编译时报错、IDE 能自动补全,重构接口时编译器会告诉你哪些调用方没跟上。这种"契约即代码"的体验,比对着 RESTful 文档手写 HTTP 调用要省心太多。用了几年之后,我是真心觉得在内部系统里维护一份文档不如维护一份 IDL。
但有一个误区要提醒:不要因为团队内部用 RPC 顺手,就把开放接口也强行做成 RPC。我见过一些项目,对外提供 SDK 时让第三方下载客户端库,结果每个语言都要维护一套 SDK,版本发布还要赶上第三方的升级节奏。最后第三方抱怨接入成本太高,自查了一下,收益根本没有想象中那么大,反而给团队背上了一个沉重的兼容包袱。
2.2 什么时候选 RESTful:生态、可读性和解耦优先
RESTful 的主场是"开放"和"长生命周期"。面向浏览器和移动端的接口,天然要被各种不可控的客户端调用,可能是五年前的旧版 App、可能是不知道哪来的脚本、可能是不同语言写的第三方系统。你没法控制它们升级,必须保证接口在多年后仍然能稳定识别、易于调试。RESTful 因为基于标准 HTTP 方法和语义化 URL,给了客户端足够多的自解释信息。
另一个重要优势是生态。几乎所有基础设施都天然理解 HTTP:网关可以做限流、鉴权、日志采集,监控系统可以直接解析状态码,负载均衡器能按 URL 路径做路由,CDN 能对 GET 请求做缓存。RESTful 接口可以低成本接入这些能力,而这正是 RPC 往往需要专门治理组件才能补齐的。
选 RESTful 还要考虑一个事实:很多业务接口的并发量和性能要求,并没有到 RPC 才能扛住的程度。一个用户服务每秒几千请求,用 RESTful + JSON 完全没问题,压测数据很好看,维护成本又比 RPC 低。不少团队一上来就上全套 RPC,结果发现性能瓶颈根本不在序列化,而在数据库查询和外部依赖,反而白白增加了一整套工具链的学习成本。
当然,RESTful 设计也不是扔给开发者就能用好。很多团队写着写着就变成"披着 RESTful 外衣的 RPC":URL 里全是动词(/getUserData、/createNewOrder),各种接口连 HTTP 状态码都用 200 表示,错误信息全部塞在业务 code 里。这等于放弃了 RESTful 最大的价值——可读性和自描述性。所以选 RESTful 的同时,也要有对应的设计规范和代码评审约束。
3. RESTful 接口设计实操要点
3.1 资源建模与 URL 设计
RESTful 设计的关键一步是资源建模,建得不好,后面整个接口都会别扭。我的习惯是先用自然语言描述业务:用户下单、订单包含商品、商品归属于商家、用户可以对订单发起退款。把这些名词圈出来,核心资源基本就是用户、订单、商品、商家、退款单。然后确定它们之间的从属关系,用 URL 层级表达。
一个比较可靠的设计套路是:核心资源用顶层 URL,从属资源用嵌套 URL,嵌套层级一般不要超过两层。比如:
/users用户集合/users/{id}单个用户/users/{id}/orders用户的订单列表/orders/{id}/items订单下的商品条目
集合用复数名词,单个对象用 ID 定位,关联关系通过嵌套表达。筛选、排序、分页这些操作,不要发明新 URL,而是用查询参数:GET /orders?status=paid&page=1&page_size=20&sort=-created_at。这样的好处是资源标识稳定,客户端只是改变查询视角,不会因为筛选条件不同而冒出无数个端点。
很多新人会纠结"那动作类操作怎么设计"?比如下单、支付、退款、确认收货。我的答案是:大部分动作都可以被理解成"创建子资源"或"修改资源状态"。下单本质上就是创建一个订单资源,支付可以设计成 POST /orders/{id}/payments 创建一笔支付单,退款可以 POST /refunds。这种设计看起来比直接 POST /payOrder 多绕了一步,但换来的是完整、可追溯的资源历史。用户可以看到订单下的支付记录、退款记录,而不是一个不知道发生了什么的状态变化。
如果真的遇到很难建模成资源的高级操作,比如批量审核、执行数据迁移、触发缓存刷新,我建议不要硬凹 RESTful。可以保留一个"动作式端点"比如 POST /batch/audit,在文档里说明即可。设计是为了好用,不是为了证明自己够 REST。
3.2 状态码、错误响应与版本管理
状态码是 RESTful 接口最容易出问题的地方之一。团队里经常出现"全部返回 200,靠业务 code 判断成功失败"的做法。短期看省事,长期看是灾难:监控系统失效,日志里全是 200,链路追踪探针也没法按状态码告警,调用方还得写一堆解析逻辑。
规范一点的做法是让 HTTP 状态码表达"这次请求从传输层看是否成功"。资源不存在用 404,参数校验不过用 400,未授权用 401,无权限用 403,服务内部异常用 500,第三方超时用 504。业务上的"领取失败""库存不足""余额不够"这些,本质上是业务状态,而不是传输状态,可以放在响应体中的业务 code 里。两者分工明确,监控和业务判断互不干扰。
响应体结构建议全项目统一,别一个接口一套格式。一个我实践下来比较顺手的模板:
json复制{
"code": 0,
"message": "success",
"data": {
"id": 10001,
"name": "某用户"
},
"trace_id": "a3f5c8e6-2f0d-4e9a-b5d6-1a9c2e7f9b3d"
}
code 是业务码,0 表示成功,非 0 表示业务失败。data 放业务数据,错误时可以是 null,也可以是错误详情对象。message 给人类读的提示,不能把堆栈信息原样抛给客户端。trace_id 用于链路追踪,客户端报问题时拿着这个 ID 就能快速查日志。
版本管理方面,最简单的方案是 URL 版本号:/v1/users、/v2/users。优点是直观、不需要额外协商,缺点是会污染 URL 语义,且老版本很难下线。另一种方案是 Header 版本号:Accept: application/vnd.example.v2+json。这种保持 URL 干净,但对调用方不友好,浏览器直接访问始终拿到默认版本。我的建议:对外公开接口用 URL 版本,内部接口尽量少做破坏性变更,用兼容性演进代替版本切换。多数情况下"新增字段、废弃旧字段"就能解决问题,没必要动不动升大版本。
还有幂等性,这个在 RESTful 接口里非常容易被忽略。特别是支付、下单这类涉及资金的场景,客户端超时重试时如果反复创建订单,后果很直接。经验做法是:POST 创建类接口支持客户端传一个 Idempotency-Key 请求头,服务端记录这个 key 对应的处理结果,重复请求直接返回第一次的结果。PUT 和 DELETE 天然具备幂等性,PATCH 要特别小心,不是所有更新都幂等。
4. RPC 接口设计实操要点
4.1 IDL 与协议选型
RPC 设计的第一步不是写代码,而是定义契约(IDL)。这份文件是服务端和所有客户端共同遵守的唯一真相。定义得好,后续开发和沟通成本都会大幅下降;定义得随意,后面每个服务的升级都是一次阵痛。
一份典型的接口定义长这样:
idl复制service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUserOrders(ListUserOrdersRequest) returns (ListUserOrdersResponse);
}
message GetUserRequest {
int64 user_id = 1;
bool with_profile = 2;
}
message GetUserResponse {
User user = 1;
}
留意几个细节。字段编号 = 1、= 2 特别重要,它决定了消息在二进制序列化中的标识,一旦发布就不允许修改,否则新旧客户端解析会出现错位。with_profile 这类"带不带某字段"的开关尽量少用,每多一个开关,服务端处理逻辑就复杂一分,建议通过不同方法实现不同粒度查询,而不是一个方法开关满天飞。
协议选型上,现在主流趋势是 HTTP/2 + 二进制序列化。HTTP/2 的多路复用能力解决了传统 RPC 的队头阻塞问题,是长连接上承载高频调用的基础。很多团队用的是带 IDL 和代码生成器的开源框架,服务端写一遍实现,客户端各语言自动生成 stubs。这个体验比手写 HTTP 调用舒服太多了,尤其是多语言环境下,加一个新客户端语言只需要跑一次代码生成,不用重写协议逻辑。
RPC 接口的兼容性规则也有讲究:新增字段是安全的,删除字段要谨慎,修改字段编号和类型属于破坏性变更。我经历过一次"看着是优化,结果线上炸了"的事故:某团队把一个 int32 改成 int64,自测各种正常,发布后新老服务混跑时,老服务解析新消息完全错位,所有接口直接返回垃圾数据。凡是序列化协议,字段类型改动必须按破坏性变更处理,必须走完整的版本发布流程。
4.2 服务治理与版本策略
RPC 的优势有一大半建立在服务治理能力上。框架里通常内置注册中心、负载均衡、重试、超时、熔断、限流这些基础组件,业务代码几乎不用感知。这也是内部接口用 RPC 的深层原因:你不仅要"能调用",还要"调得稳"。
要特别注意超时和重试的配置。重试不是越多越好。读接口重试两三次没问题,但写接口一旦重试就可能造成重复数据。更危险的是"重试风暴":服务 A 调用服务 B 超时,A 重试,B 也超时后在内部又掉头调用 C,整个链路所有节点同时重试,瞬间把下游打挂。我见过一个事故,上游节点并发不高,但因为每层都配置了 3 次重试,实际打到数据库的流量涨了 9 倍,直接把连接池打满。所以重试一定要有退避策略(比如指数退避 + 随机抖动),同时严格限定只对幂等操作开启重试。
熔断策略同样讲究。常见的"连续失败 5 次就熔断 10 秒"这种固定数值适合经验不多的团队起步,但对波动较大的业务不太够用。更合理的是基于错误率做滑动窗口统计,比如"统计 10 秒内请求数,如果错误率超过 50% 就熔断"。还要注意熔断后的降级策略,不是熔断了就抛异常完事,而是要有兜底数据或缓存结果。内部服务之间可以接受短暂降级,不能接受雪崩。
RPC 版本管理比 RESTful 更依赖工具链。主流做法是同时部署多个版本的服务实例,通过注册中心路由把流量按比例灰度切到新版本。调用方兼容方面,靠 IDL 的向前向后兼容来保证:新版本服务要能处理旧版请求,旧版客户端要能调用新服务。如果确实做了破坏性变更,需要新老服务分命名空间部署,等所有调用方迁移完成后再下线旧版本。在下线之前,一定要在监控里确认旧版本调用量真正归零,而不是靠感觉。 我就见过一次,旧服务下线后第二天,某条定时任务还在发老版本请求,瞬时错误率飘红,排查半天才想起这事。
5. 融合实践:一套系统里让 RPC 与 RESTful 各司其职
5.1 内外网分隔与 API 网关
很多团队把 RPC 和 RESTful 对立起来,其实正确的解法是分层共存:对外保留一套稳定、开放、可读性强的 RESTful API;对内使用高效、强契约、治理完善的 RPC 服务。两者不是二选一,而是分别服务于不同的流量入口。
一个很常见的融合架构是:外部请求 → API 网关 → BFF(Backend For Frontend)层 → 内部 RPC 服务。API 网关负责统一鉴权、限流、SSL 终止、报文日志;BFF 层是"翻译官",接收 RESTful 请求,编排调用多个内部 RPC 服务,再把结果组装成适合客户端的响应结构。用户下单这个动作,对外是 POST /v1/orders,对内 BFF 会调一个下单 RPC 服务,甚至继续调用库存、优惠、通知等多个内部服务。
我自己实践下来的体会是:BFF 层要尽量"薄"——只做协议转换和轻量编排,不要放太多业务逻辑。业务逻辑一旦下沉到 BFF,就会出现"胖 BFF",最终多个应用把逻辑复制粘贴一遍,改一处漏三处。保持 BFF 只做代理和聚合,业务规则全部留在 RPC 服务的实现里,整个系统才能维持清晰的边界。
网关层的鉴权也会遇到"身份传递"问题。对外是一套 Token,对内服务又各自不同鉴权方式。常规做法是网关解析并校验外部 Token 后,生成一个内部身份的令牌或结构化上下文,塞进 RPC 请求头里透传给链路中的每个服务。各内部服务只需要信任网关,不需要再解析外部 Token。这个设计大大简化了内部服务的鉴权逻辑,但也带来一个要求:网关必须是整个系统暴露给外界的唯一入口,任何绕过网关的调用都是安全漏洞。
5.2 双协议暴露与代码生成
如果觉得"网关 + BFF"太重,还有一个更轻量的融合方式:同一套服务同时暴露 RPC 端口和 RESTful 端口。现在不少 RPC 框架都支持从 IDL 出发生成对应的 RESTful API 映射,甚至能自动生成 OpenAPI 文档。对于外部确实需要、但内部也想用 RPC 的服务,这个方案成本最低。
具体做法是:IDL 里定义方法后,通过注解或配置文件声明该方法对应的 REST 映射关系。比如"GetUser"方法映射到"GET /v1/users/{user_id}","CreateUser"映射到"POST /v1/users"。框架会在服务启动时自动注册两套监听器,RPC 流量走内部端口,RESTful 流量走外部端口。内部服务间调用依旧走 RPC 获得高性能,外部调用方使用标准的 HTTP 接口,不需要下载任何 SDK。
这个方案要踩的坑主要是"映射粒度"。RPC 方法往往是细粒度的内部逻辑(比如 GetUserWithOrdersAndCoupons),直接暴露成 HTTP 接口会泄露内部实现细节,URL 设计也很不 RESTful。我的建议是:双协议暴露只适合真正和外部交互的边界服务;内部细粒度方法不要为了"顺便"暴露而暴露,宁可让 BFF 层重新组织一个粗粒度的对外方法。接口是契约,暴露出去的外网契约要谨慎到可以跑十年不重构。
还有响应差异问题。RPC 的响应体是强类型消息,转换到 JSON 时字段名可能变成下划线或驼峰,嵌套结构也可能很复杂。这通常需要配置转换规则。尤其要注意"时序":有些二进制协议不保证 map 的迭代顺序,但 JSON 是讲究顺序的,转换时如果不做稳定排序,客户端拿到的字段顺序每次可能不一样。这个问题排查起来非常隐蔽,表现形式就是"客户端有时好有时坏"。
5.3 混合协议切换的过渡策略
还有一种更现实的场景:系统已经在用 RESTful,想往 RPC 迁移,又不想一夜之间全部切换。成体系的迁移策略是这样:先把新服务的内部调用用 RPC 实现,旧服务保留 RESTful;在网关上做按流量比例灰度,比如 10% 的流量走新链路,90% 走旧链路。灰度期间同时监控成功率、P99 延迟和错误日志,确认稳定后逐步调高比例,直到全量切换。
我特别要强调:混合协议期间最容易出问题的是 SDK 版本和调用方兼容性。如果同一个消费者服务有的实例升级了 SDK、有的没有,新旧逻辑混跑,容易产生"半新半旧"的中间状态。轻则是日志对不上,严重时可能出现双写冲突。所以灰度迁移前,最好先统一消费者 SDK 版本,再切流量。
另外,RESTful 时代的超时、重试、鉴权逻辑,在 RPC 体系里不能简单平移。比如 RESTful 你可以依赖网关统一超时配置,但 RPC 超时通常在框架配置里逐接口设置,开关不同,效果完全不同。迁移前要专门花时间核对一份"配置对照表",逐项确认超时、重试次数、熔断阈值、连接池大小和监控大盘是否已覆盖。很多人迁移失败不是协议本身的问题,而是配置和运维体系没跟上。
6. 常见问题与排查技巧实录
6.1 高频踩坑清单
结合自己和身边团队的实际经历,我把接口设计里最常遇到的问题整理成一个速查表。这些问题无论在纯 RPC 还是纯 RESTful 系统里都容易出现。
| 问题表现 | 根本原因 | 排查思路 | 解决方向 |
|---|---|---|---|
| RPC 接口偶发超时,重试后成功 | 连接池不够或单连接被阻塞 | 查看连接池大小、GC 停顿、下游慢调用 | 调大连接池、拆分方法、下游做限流 |
| 重试导致下游雪崩 | 写操作盲目重试,无退避策略 | 核对框架重试配置、统计重试次数 | 只对幂等操作重试,启用指数退避 |
| RESTful 接口返回全是 200 | 业务码代替 HTTP 状态码 | 看网关日志和监控状态码分布 | 明确状态码语义,业务码只表业务状态 |
URL 出现 /getUserData 之类动词 |
资源建模不到位 | 梳理名词与动词,重新画资源矩阵 | 按资源重写 URL,动作映射为子资源 |
| 新老版本序列化错位 | 字段编号或类型被修改 | 查看 IDL 变更记录和兼容性检查报告 | 冻结字段编号,破坏性变更走版本发布 |
| 网关转发后拿不到真实客户端 IP | 缺少 X-Forwarded-For 处理 | 检查网关转发配置和日志字段 | 添加可信 IP 透传头,并在网关注入原始 IP |
| 双协议暴露后字段顺序不稳定 | 二进制 map 序列化不保证顺序 | 对比响应 JSON 多次差异 | 转换层增加稳定排序规则 |
| 熔断后所有请求异常 | 熔断后没有降级预案 | 查降级方法和缓存是否配置 | 熔断开启时有兜底数据或默认结果 |
| 客户端报错信息只有"B 服务异常" | 错误信息被包装吞掉 | 查链路追踪 trace_id、各层异常映射 | 统一错误码映射,保留原始错误上下文 |
这个表里的每一个问题,我基本都亲眼见过或者亲手排查过。最想提醒的是第一类问题:RPC 的"偶发超时"往往不是网络问题,而是系统性问题的一个表象。不要一上来就调重试次数,先把错误率、GC 停顿、下游 P99 拉出来看,找到真正的瓶颈再动手。
排查这类问题,我有一套比较固定的动作:先确认是否只有特定方法超时,还是所有方法都超时;再确认超时集中在哪个时间段,是不是和大促、定时任务、数据补偿任务重叠;接着看下游服务的指标面板,确认下游本身有没有变慢;最后再看连接池和线程池指标。这套顺序能避免很多"凭感觉修"的弯路。
6.2 三个少有人说的实操心得
第一,设计接口之前先画资源/方法矩阵。我会拿一张纸,把系统的核心实体列在横轴,把核心操作列在纵轴,每个交叉点标注"是否需要接口、属于读还是写、是高频还是低频"。这个方法看起来笨,但对资源建模特别有效。它能逼着你把隐藏实体挖出来,比如"退款单""支付单"平时不是主体的概念,但在矩阵里你会发现操作很多,值得单独建模。
第二,兼容性比规范本身更重要。接口设计规范再完美,只要有一次破坏性变更伤了调用方的心,整个规范的可信度就受损了。我见过一些团队,规范文档写了厚厚一本,但接口升级时照样把字段删得干净利落。我更愿意把"兼容性审查"当作设计评审的固定环节:任何接口变更,第一反应不是"怎么改",而是"老调用方会怎样"。新增字段是默认安全的,废字段要至少保留到所有已知调用方确认不再使用。
第三,用契约测试守住接口边界。单纯靠评审和自觉防不住接口腐化。现在的做法是在 CI 流水线里加入契约测试:客户端验证服务端返回的响应结构是否满足自己定义的契约,服务端验证自己是否还能被老客户端使用。一旦有人改了字段类型或删了字段,测试直接失败,从机制上挡住违反兼容性的改动。如果把接口当作品质保障对象,契约测试就是接口的"单元测试"。
最后分享一个个人的小经验:我在做某个订单系统的接口升级时,最有效的不是争论协议选型,而是先在 IDL 里把对外能力定义清楚,再由工具生成 RESTful 映射,内部服务之间维持 RPC 调用。半年之后回看,暴露出去的 RESTful 接口一直没有大的改动,内部的 RPC 接口却被优化重构了好几次。接口设计的自由度在内部,稳定性在外部,把这两件事分开,系统就能同时收获灵活和可靠。
