需求分析与架构设计:从模糊想法到可落地的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 模块分层架构 工具层(Utils) 签名计算 · 时间格式化 · 数据校验 领域层(Domain) 支付模块 下单 · 退款 · 对账 用户模块 登录 · 注册 · 信息查询 订单模块 创建 · 查询 · 状态变更 核心层(Core) HTTP 客户端 连接池 · 超时 序列化 JSON · Protobuf 认证 签名 · Token 重试 策略 依赖方向:Core ← Domain ← Utils(上层依赖下层,下层不依赖上层)

你看,核心层在最底下,谁都不依赖。领域层依赖核心层,但领域模块之间不能互相调用。工具层在最上面,提供一些通用能力。这样做的好处是,哪天你想把支付模块单独抽出来做成一个轻量SDK,直接拿走就行,不会牵连其他模块。

接口定义规范:好的API是「猜得到」的

接口设计是我最在意的事。一个好的SDK接口,用户不用看文档就能猜出七八分怎么用。怎么做到?

我总结了几个硬性规范:

接口命名三原则:

  1. 动词+名词:比如 createOrder()、queryUser(),别用 doAction() 这种模糊命名。
  2. 参数对象化:超过3个参数就用对象传参。我见过一个接口有12个参数,调用方每次都要对着文档数位置。
  3. 返回值统一:所有接口返回统一的结果包装,比如 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要友好到让用户不用动脑子。