4. 组件接口设计:接口的定义与分类
接口设计这件事,说白了就是给组件画「边界」。
我早年带团队时,有个项目就是因为接口没定义清楚,两个模块互相等着对方的数据格式。结果呢?联调阶段直接炸了,加班两周才搞定。从那以后,我对接口设计就特别较真。
今天咱们聊聊接口设计的三个核心问题:接口怎么分类、粒度怎么控制、版本怎么管。
4.1 接口的定义与分类
接口不是只有一种。我习惯把它分成两大类:提供接口和需求接口。
核心观点:一个组件的接口,就是它对外承诺的「服务」和「索取」。
4.1.1 提供接口(Provided Interface)
提供接口,就是组件能「给」别人什么。比如一个订单组件,它提供「创建订单」、「查询订单」、「取消订单」这些能力。
我一般这样定义提供接口:
- 操作名称:动词+名词,比如
createOrder - 输入参数:调用方需要传什么
- 输出结果:组件返回什么
- 异常情况:什么情况下会报错
// 提供接口示例(TypeScript)
interface OrderService {
// 创建订单
createOrder(request: CreateOrderRequest): CreateOrderResponse;
// 查询订单详情
getOrder(orderId: string): OrderDetail;
// 取消订单
cancelOrder(orderId: string, reason: string): void;
}
4.1.2 需求接口(Required Interface)
需求接口正好反过来——组件需要别人「给」它什么。比如订单组件需要调用支付组件、库存组件,这些依赖关系就是需求接口。
嗯,这里要注意:需求接口往往被新手忽略。我见过太多人只画提供接口,结果组件一部署,发现缺了一堆外部依赖。
// 需求接口示例
interface OrderRequiredServices {
// 需要支付服务
paymentService: PaymentService;
// 需要库存服务
inventoryService: InventoryService;
// 需要通知服务
notificationService: NotificationService;
}
4.2 接口粒度控制
接口粒度这个问题,说白了就是「一个接口该管多少事」。
我踩过一个大坑:有个项目把用户的所有操作都塞到一个接口里,叫 UserFacade。结果呢?这个接口有 30 多个方法,调用方根本搞不清该用哪个。后来重构时,我把它拆成了 UserQueryService、UserCommandService、UserAuthService 三个接口,世界清净了。
关于粒度,我总结了三条原则:
| 原则 | 说明 | 反面案例 |
|---|---|---|
| 单一职责 | 一个接口只做一类事 | 把查询和修改混在一起 |
| 内聚性 | 接口内的方法要相关 | 订单接口里放了个用户登录方法 |
| 可测试性 | 接口要容易 mock | 一个接口依赖 10 个外部服务 |
我的习惯:一个接口的方法数控制在 5-8 个。超过 10 个,我就会考虑拆分。你想想看,一个接口 20 个方法,调用方得多痛苦?
4.3 接口版本管理策略
接口一旦发布,就不能随便改了。但业务总在变,怎么办?版本管理就是干这个的。
4.3.1 版本号规范
我推荐用语义化版本号:主版本.次版本.补丁版本
- 主版本:不兼容的变更。比如删了某个接口、改了参数结构
- 次版本:向下兼容的新功能。比如新增一个可选参数
- 补丁版本:内部修复,接口不变
// 版本管理示例
// v1.0.0 - 初始版本
interface OrderServiceV1 {
createOrder(items: Item[]): OrderId;
}
// v1.1.0 - 新增优惠券支持(向下兼容)
interface OrderServiceV1_1 {
createOrder(items: Item[], coupon?: string): OrderId;
}
// v2.0.0 - 不兼容变更,改用异步模式
interface OrderServiceV2 {
createOrder(request: CreateOrderRequest): Promise<OrderResult>;
}
4.3.2 兼容性策略
我曾经遇到一个场景:支付接口要升级,但上游有 50 多个调用方。直接改?不可能。我用了「双版本并行」的策略:
- 旧版本保留:v1 接口继续运行,标记为 deprecated
- 新版本上线:v2 接口同时提供服务
- 灰度迁移:调用方逐个切换到 v2
- 旧版本下线:所有调用方迁移完成后,下线 v1
注意:千万不要同时维护超过两个主版本。我见过一个项目同时维护 v1、v2、v3、v4,代码里全是 if-else,维护成本直接爆炸。
4.4 知识体系总览
下面这张图,把接口设计的核心逻辑串起来了:
4.5 避坑指南
最后,分享几个我亲身踩过的坑:
- 我曾经把接口定义得特别「灵活」,参数全是
Map<String, Object>。结果调用方传什么格式的都有,解析代码写了一堆 if-else。后来我改成强类型 DTO,问题迎刃而解。 - 我曾经为了省事,直接修改了已发布的接口。结果下游服务没同步更新,线上出了 P0 事故。从那以后,我严格执行「接口只增不改」的原则。
- 我曾经觉得接口文档可有可无。直到有个新同事问我「这个参数是传 JSON 还是 XML」,我才意识到文档的重要性。现在我用 OpenAPI 规范,接口定义即文档。
一个小建议:接口设计阶段,多花 30 分钟把边界画清楚,能省下后面 3 天的联调时间。这笔账,怎么算都划算。