接口设计规范:RESTful vs RPC、参数校验、版本控制

接口设计这个事,说起来简单,做起来坑不少。我个人习惯把接口设计分成三个维度:风格选择、参数校验 和 版本控制。这三个环节要是没想清楚,后面的联调、维护能让你头疼好几个月。

先别急着写代码。你想想看,一个对外输出的 SDK,接口就是门面。门面歪了,内部逻辑再漂亮也白搭。今天咱们就聊聊我这些年踩过的坑和总结下来的套路。

RESTful vs RPC:风格之争

接口风格这事儿,圈里争了好多年。我接手过一个项目,后端团队一半用 RESTful,一半用 RPC,联调的时候简直像两个星球的人在说话。说白了,没有的好坏,关键是看场景。

维度 RESTful RPC
核心思想 资源导向,通过 HTTP 动词(GET/POST/PUT/DELETE)操作资源 动作导向,像调用本地函数一样调用远程方法
URL 设计 /users/123 表示用户资源,GET 查询,POST 创建 /user.getById 或 /UserService.getUser
状态码 充分利用 HTTP 状态码(200、404、500 等) 一般统一返回 200,内部用 error code 区分
序列化 JSON / XML 为主,自描述 Protobuf / Thrift / Hessian 等二进制格式,性能高
适用场景 开放 API、C/S 架构、资源模型清晰 内部微服务、高性能、强类型要求的系统
学习成本 较低,规范固定 较高,需要定义 IDL(接口定义语言)
我的建议:对外 SDK 优先 RESTful,因为开发者天然理解 HTTP 语义。内部服务调用,如果对延迟敏感,RPC 更合适。我曾经在一个项目里两边都用,结果接口文档写了 200 页,大家还是经常搞混。

选型时还有个重要因素:版本控制。RESTful 可以通过 URL 路径或 Header 来区分版本,RPC 一般依赖 IDL 的兼容性设计。这部分下面细说。

参数校验:该做到的不要偷懒

「防御性编程」这个词大家都听过,但实际写代码时很容易忽略。我遇到过最离谱的事:用户传了个负数做分页参数,服务端没校验,直接导致内存溢出。你说冤不冤?

参数校验应该是 SDK 的第一道防线。我建议至少做这么几层:

  • 类型校验:参数是否是期望的类型?字符串别当数字传。
  • 范围校验:数值是否在合理区间?比如分页大小最大 100。
  • 格式校验:邮箱、手机号、日期格式是否合法?
  • 业务逻辑校验:比如删除接口,得先确认资源是否存在。

举个 Java 例子,用 JSR 303 Bean Validation 注解:

public class CreateUserRequest {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Min(value = 1, message = "年龄必须大于0")
    @Max(value = 150, message = "年龄不能超过150")
    private int age;

    // getter/setter ...
}

然后在控制器里加个 @Valid 就行了,框架自动返回 400 和错误信息。

注意:参数校验代码不要全用框架,核心业务逻辑里也要加。尤其是 SDK 提供的工具方法,别假设调用方一定会传合法参数。我曾经写过一个签名工具,没检验参数非空,结果线上 app 传了个 null 进去,直接空指针崩溃。

版本控制:不给升级留尾巴

接口一变,客户端不升级就崩——这是 SDK 开发者的噩梦。版本控制就是用来解决这个问题的。常见的策略有几种:

  1. URL 路径版本号: /v1/users、/v2/users。最直观,但 URL 会膨胀。
  2. 请求头版本号: 比如 Accept: application/vnd.myapp.v2+json。干净,但调试麻烦。
  3. 查询参数版本号: /users?version=2。容易缓存冲突,不推荐。
  4. 自定义 Header 版本号: 比如 X-API-Version: 2。灵活,但需要客户端配合。

我个人习惯在 SDK 内部维护一个版本路由表,把不同版本的处理器注册进去。例如:

// 伪代码
Map<String, VersionHandler> versionHandlers = new HashMap<>();
versionHandlers.put("v1", new V1UserHandler());
versionHandlers.put("v2", new V2UserHandler());

public void handleRequest(String version, Request request) {
    VersionHandler handler = versionHandlers.get(version);
    if (handler == null) {
        throw new UnsupportedVersionException("不支持的版本: " + version);
    }
    handler.process(request);
}

这样新版本加进来,旧版本还在,客户端可以平滑迁移。记住一个原则:永远不要强制客户端立即升级,要给至少一个版本的重叠周期。

一张图看懂接口设计规范

下面这张图总结了我刚才讲的核心思路。包括风格选择、参数校验流程和版本控制的决策点。嗯,画图的时候我花了点心思,把常见坑也标出来了。

接口设计规范知识体系图 接口设计规范核心模块 风格选择 参数校验 版本控制 RESTful RPC 类型校验 范围校验 格式校验 URL路径版本 Header版本 决策因素 • 开放 vs 内部:对外用RESTful,对内用RPC • 频繁升级:使用Header版本号避免URL膨胀 • 性能敏感:RPC+Protobuf • 参数校验必须前置,不做甩锅侠 实践流程建议 1. 根据场景选定风格(RESTful / RPC) 2. 定义接口契约(OpenAPI / IDL) 3. 实现参数校验层(前置 + 业务) 4. 设计版本策略,预留平滑升级通道
这张图你保存下来,做新接口设计时拿出来对照一遍,能省不少事。我记得有次画完这个图,团队里新人都能直接上手了,再也没出现过接口风格混乱的问题。

小结

接口设计规范不是教条,是无数趟坑之后总结出来的经验。RESTful 和 RPC 没有的优劣,关键看你的使用场景。参数校验别偷懒,那是最后一道防线。版本控制要提前想好,否则后面改一个字段,客户端全挂。

养成好习惯,从每一个接口设计开始。