需求分析与架构设计:从模糊想法到可落地的SDK骨架
说实话,很多团队做SDK失败,不是代码写得烂,而是第一步就走歪了。需求没理清,架构就跟着飘。今天咱们就聊聊,怎么把那些零散的「我想要个SDK」变成一张可执行的蓝图。
需求收集与分析:别让「我以为」毁了项目
我见过最惨的案例是什么?产品经理拍脑袋说「对标AWS SDK」,结果开发了三个月,发现客户最需要的只是一个发短信的接口。嗯,需求收集这件事,说白了就是搞清楚三件事:谁在用、怎么用、用在哪。
需求收集的核心三板斧:
- 用户画像:你的SDK是给前端小哥用的,还是给后端架构师用的?这两类人对API的期望完全不同。
- 场景还原:别问「你需要什么功能」,要问「你当前怎么解决这个问题的」。我有个习惯,每次访谈都让客户现场写一段伪代码。
- 优先级排序:用MoSCoW法则(Must have / Should have / Could have / Won't have)把需求分四类。
举个例子。我之前做支付SDK时,客户说「我要支持所有主流支付方式」。我一听就知道这是个坑。追问下去才发现,他们80%的交易走和支付宝,剩下的20%里还有一半是银行卡。最后我们把「Must have」定成了、支付宝、银联,其他全扔进「Could have」。
我的一个小技巧:需求文档里一定要写「不做什么」。这比「做什么」更重要,能避免后期无休止的需求蔓延。
架构设计原则:SDK不是微服务,别搞复杂了
SDK架构和后台架构完全是两码事。后台你可以拆成几十个微服务,SDK不行。SDK是要嵌入到别人代码里运行的,你想想看,用户引入一个SDK,结果它自己起了个线程池、连了个数据库,那不乱套了?
我个人习惯遵循这几个原则:
| 原则 | 说明 | 我在项目中踩过的坑 |
|---|---|---|
| 最小依赖 | SDK的第三方依赖越少越好,最好零依赖 | 曾经因为依赖了某个日志框架,和用户项目里的版本冲突,排查了两天 |
| 无状态 | SDK内部不保存业务状态,所有状态由调用方管理 | 有次在SDK里缓存了token,结果用户多线程调用时token过期了还在用 |
| 可测试性 | 每个模块都能独立mock和测试 | 早期没注意这点,导致自动化测试覆盖率一直上不去 |
| 渐进式复杂度 | 简单场景一行代码搞定,复杂场景才暴露高级配置 | 这个后面接口定义部分会细说 |
警告:千万不要在SDK里做「自动重试+指数退避」的默认实现。我见过一个SDK,网络波动时自动重试了5次,直接把用户的服务端打挂了。重试策略应该交给调用方决定。
模块划分:高内聚、低耦合,但别过度设计
模块划分这事,我吃过不少亏。一开始总想搞个「万能SDK」,一个模块管所有。后来发现,改一个功能要动整个包,发版时心惊胆战。
现在我的做法是:按「职责」切,不按「功能」切。什么意思?
- 核心层(Core):HTTP客户端、序列化、认证、重试策略。这是SDK的骨架,所有模块都依赖它。
- 领域层(Domain):按业务领域划分,比如支付模块、用户模块、订单模块。每个模块只知道自己该做的事。
- 工具层(Utils):一些通用的辅助函数,比如签名计算、时间格式化。注意,这一层不能依赖任何领域模块。
下面这张图能帮你快速理解分层关系:
你看,核心层在最底下,谁都不依赖。领域层依赖核心层,但领域模块之间不能互相调用。工具层在最上面,提供一些通用能力。这样做的好处是,哪天你想把支付模块单独抽出来做成一个轻量SDK,直接拿走就行,不会牵连其他模块。
接口定义规范:好的API是「猜得到」的
接口设计是我最在意的事。一个好的SDK接口,用户不用看文档就能猜出七八分怎么用。怎么做到?
我总结了几个硬性规范:
接口命名三原则:
- 动词+名词:比如
createOrder()、queryUser(),别用doAction()这种模糊命名。 - 参数对象化:超过3个参数就用对象传参。我见过一个接口有12个参数,调用方每次都要对着文档数位置。
- 返回值统一:所有接口返回统一的结果包装,比如
Result<T>,里面包含 code、message、data。
来看一个实际例子。假设我们要设计一个「创建订单」的接口:
// ❌ 错误示范:参数太多,顺序容易搞错
Order createOrder(String userId, String productId, int quantity,
String couponId, String address, String remark,
boolean useInsurance, int deliveryType);
// ✅ 正确示范:参数对象化,返回值统一
Result<Order> createOrder(CreateOrderRequest request);
// 请求对象
class CreateOrderRequest {
private String userId; // 必填
private String productId; // 必填
private int quantity; // 必填,默认1
private String couponId; // 选填
private String address; // 选填,默认用户默认地址
private String remark; // 选填
private boolean useInsurance; // 选填,默认false
private int deliveryType; // 选填,默认快递
}
避坑指南:我曾经在接口里用了枚举作为参数类型,结果用户升级SDK后枚举值变了,编译直接报错。后来我改成字符串+文档说明,虽然「不优雅」,但兼容性好得多。SDK的兼容性比代码优雅重要100倍。
另外,接口版本管理也得提前想好。我推荐两种方式:
| 方式 | 做法 | 适用场景 |
|---|---|---|
| URL路径版本 | /v1/order/create → /v2/order/create |
接口变动较大,新旧版本共存时间长 |
| 请求头版本 | 通过 X-API-Version: 1.0 指定 |
内部SDK,版本迭代快,不想暴露URL |
我个人更倾向URL路径版本。虽然看起来「不干净」,但用户一眼就能看出自己在用哪个版本,排查问题也方便。
总结一下
需求分析阶段,多花一周时间访谈客户,能省下后面三个月的返工。架构设计阶段,记住「少即是多」,SDK不是炫技的地方。接口定义阶段,把用户当傻瓜——这不是贬义,而是说你的API要友好到让用户不用动脑子。