场景设计:跨端SDK架构深度解析
场景设计题:如何设计一套跨端 SDK——插件化、依赖注入与多端适配。 给出架构范式,掌握后能应对「设计一个跨端基础设施」的开放题。
场景设计:跨端SDK架构深度解析
一句话概括
跨端 SDK 的本质是把”会变的部分”封进适配器,”不变的部分”固化成核心——你给每个平台写一遍网络请求/存储/设备信息,但事件路由、队列、重试逻辑只写一次。
核心知识点
1. 四层架构模型
1
2
3
4
5
6
7
// ① API 层:对外接口,跨端一致
// ② Core 层:纯业务逻辑,零平台代码(没有 import UIKit / wx.xxx)
// ③ Adapter 层:每个平台实现一套(实现 IPlatformAdapter 接口)
// ④ Platform 层:平台原生能力(网络、存储、线程)
// 关键约束:Core 层永远通过接口依赖 Adapter,不允许直接 import 平台代码
// 这就是依赖注入(DI)+ 控制反转(IoC)在 SDK 里的应用
2. 核心接口定义
1
2
3
4
5
6
7
8
// IPlatformAdapter —— Core 唯一能接触的平台抽象
interface IPlatformAdapter {
network: { post(url: string, data: any): Promise<boolean>; };
storage: { getItem(k: string): Promise<string|null>; setItem(k: string, v: string): Promise<void>; };
device: { getPlatform(): string; getOSVersion(): string; getNetworkType(): string; };
lifecycle: { onForeground(cb: () => void): void; onBackground(cb: () => void): void; };
}
// 每个平台实现这个接口 → Core 自动适配
3. 事件队列:保证不丢数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TrackerQueue {
private events: TrackEvent[] = [];
push(event) {
this.events.push(event);
if (this.events.length > 10000) this.events.shift(); // 防内存溢出
this.persist(); // 异步写入本地存储,App 被杀也不丢
}
drain(count) {
const batch = this.events.splice(0, count);
// 发送失败时:reEnqueue(batch) —— 放回队列头部保证顺序
return batch;
}
}
// 核心思路:push → 持久化 → drain → 发送成功 → 删除持久化
// ↓ 发送失败 → reEnqueue → 下次再试
4. 插件系统:三明治拦截
1
用户调用 track() → PluginA.beforeTrack → PluginB.beforeTrack → Core.track → PluginA.afterTrack → PluginC.afterTrack → 网络上传
1
2
3
4
5
6
interface ITrackerPlugin {
name: string;
beforeTrack?(event): boolean | void; // return false 拦截此事件
afterTrack?(event): void;
}
// 例子:IAP 插件在 beforeTrack 中校验 transaction_id,不合法就拦截
5. 数据压缩:字段名短化
1
2
3
4
// 每个事件体积减少约 40%
const mapping = { event: 'e', properties: 'p', timestamp: 't', uuid: 'u' };
// JSON 超过 1KB 再用 gzip 压缩
// 小程序端效果尤其明显:一次 setData 的 300 条事件从 60KB 降到 12KB
其实你每天都在用
- Sentry SDK:就是这套架构的经典实现——Core 层完全平台无关,每个平台实现各自的 Transport/Scope
- Firebase Analytics:你调用的
logEvent()背后就是队列 + 批量 + 定时上报的逻辑 - React Native 的 NativeModules:本质上就是 Adapter 层,把原生能力以固定接口暴露给 JS Core
- Axios 的 adapter:
axios.defaults.adapter在 Node 用 http、浏览器用 XMLHttpRequest——同样的思路 - 微信小程序的
wx.request:它本身就是平台 Adapter 的 network 实现,你不能在 Web 端用
常见误解(FAQ)
❌ 误区一:”写 SDK 就是写一个函数库”
SDK 比函数库多了三个维度:生命周期管理(init/flush/shutdown)、宿主环境不可控(不能 crash 宿主的 App)、向后兼容(老版本 API 不能删)。你改自己的业务代码改坏了最多自己项目崩,SDK 改坏了是所有接入方崩。
❌ 误区二:”Core 层写好了各端自然一样”
接口定义是一回事,各端行为是另一回事。小程序的存储上限是 10MB、iOS UserDefaults 无严格上限、Android SharedPreferences 的 apply() 是异步的——同一个 setItem 在这三端的物理行为完全不同。测试必须覆盖各端真实环境。
❌ 误区三:”包体积不重要,现在手机存储都很大”
对于 SDK 来说包体积是硬指标。接入方会因为你的 SDK 多 200KB 就放弃集成——尤其是小程序包体积受限(主包 ≤ 2MB)。Tree-shaking、字段名短化、非核心功能拆分插件,这三个手段必须做。
一句话总结
写业务代码是盖一间房,写 SDK 是盖一个地基——你永远不知道上面会盖几层、用什么材料,所以地基只做最基础的事,剩下的交给插件。
本文由作者按照 CC BY 4.0 进行授权