文章

场景设计:跨端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 进行授权