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 多个调用方。直接改?不可能。我用了「双版本并行」的策略:

  1. 旧版本保留:v1 接口继续运行,标记为 deprecated
  2. 新版本上线:v2 接口同时提供服务
  3. 灰度迁移:调用方逐个切换到 v2
  4. 旧版本下线:所有调用方迁移完成后,下线 v1

注意:千万不要同时维护超过两个主版本。我见过一个项目同时维护 v1、v2、v3、v4,代码里全是 if-else,维护成本直接爆炸。

4.4 知识体系总览

下面这张图,把接口设计的核心逻辑串起来了:

组件接口设计知识体系 组件接口设计 接口分类 提供接口(Provided) 需求接口(Required) 粒度控制 单一职责原则 方法数 5-8 个 高内聚、可测试 版本管理 语义化版本号 双版本并行策略 最多维护 2 个主版本 核心:定义清晰 → 粒度适中 → 版本可控

4.5 避坑指南

最后,分享几个我亲身踩过的坑:

  • 我曾经把接口定义得特别「灵活」,参数全是 Map<String, Object>。结果调用方传什么格式的都有,解析代码写了一堆 if-else。后来我改成强类型 DTO,问题迎刃而解。
  • 我曾经为了省事,直接修改了已发布的接口。结果下游服务没同步更新,线上出了 P0 事故。从那以后,我严格执行「接口只增不改」的原则。
  • 我曾经觉得接口文档可有可无。直到有个新同事问我「这个参数是传 JSON 还是 XML」,我才意识到文档的重要性。现在我用 OpenAPI 规范,接口定义即文档。

一个小建议:接口设计阶段,多花 30 分钟把边界画清楚,能省下后面 3 天的联调时间。这笔账,怎么算都划算。


可可精品资料库