从项目内埋点到可复用的SDK,我这周真正完成了什么?

6 阅读7分钟

这一周我接到一个需求:领导要求我把现在做的 app 项目里已经做完、也已经跑通的埋点功能,整理成一套跨项目可复用的 SDK。

刚听到这个需求时,我对它的理解很简单,就是两步:

  • 把项目里跟埋点有关的公共代码拿出来
  • 再整理一下,做成一个给别的项目接入的sdk

可真正开始进行sdk设计的时候,我发现没那么简单了,我主要考虑的是以下几个问题:

  • 现在这个 App 项目里,到底哪些埋点能力已经在真实业务里跑通并上线了
  • 这些能力里,哪些在不同项目里大同小异,值得单独抽出来给别的项目用
  • 哪些东西虽然现在也混在这套代码实现里,但本质上还是当前项目自己的一套事件和字段规则,根本不能顺手一起塞进 SDK
  • 更关键的是,SDK 写出来以后,怎么把项目里原来用户属性和公共事件属性相关的代码,稳稳地用 sdk 替换过去

这次 SDK 封装的起点,不是空想,而是先有一个已经跑通并上线的 App 项目

这次工作有一个很重要的前提:我不是先凭空设计一个 SDK,再去找项目落地。

真实顺序正好反过来。

在开始抽 SDK 之前,我已经先在一个真实 App 项目里,把整套埋点功能做完整并完成上线。也正因为这套能力已经在真实项目里跑过,我后面才有机会真正分清两件事:

第一,哪些只是“这个项目刚好这么写了”。
第二,哪些已经稳定到可以拿去给别的项目继续用。

用户数据属性和公共事件属性,就是最典型的一类。

不同 App 项目之间,这部分东西其实经常差不太多:

  • 用户属性要补哪些基础信息
  • 公共事件属性要带哪些设备、系统、网络、安装相关字段
  • 一些字段值要从哪里取
  • 一些底层能力要怎么对齐

而且很多值,本来就主要依赖 Thinking SDK 或同类设备属性能力去拿。

这也意味着,如果每个 App 项目都各自再写一套“用户属性怎么取”和“公共属性怎么补”的逻辑,后面一定会不断重复:

  • 重复写同样的字段映射
  • 重复处理同样的时间格式
  • 重复补同样的设备属性
  • 重复处理同样的发送、失败、重试问题

也正因为这套能力已经在真实项目里做出来、跑通过、验证过,领导才会在这个时间点跟我提要求:不要让每个项目都继续单独写一套,把已经成熟的那部分能力抽取成为 SDK,后面别的项目直接复用。

这次工作真正难的,不是封装,而是先把哪些内容适合放进 SDK、哪些内容还得留在当前项目里看清楚

很多时候,一说“把项目里的实现整理成 SDK”,很容易下意识理解成“把现有实现里公共的代码拿出来”。

如果只是觉得哪些东西现在看起来比较通用,就直接把它们放进 SDK,后面一定会出问题。

因为当前项目里的这套埋点实现,本来就不只是一个单纯的埋点上报工具。它里面混着的东西非常多:

  • 事件名
  • 字段映射
  • 公共属性
  • 用户属性
  • 时间处理
  • 发送逻辑
  • 重试逻辑
  • 埋点日志
  • 业务计算
  • 当前项目页面里的具体埋点规则

这时候真正要先拆开的,不是代码文件,而是分界线。

于是我把这套东西拆成了两类。

一类是不同项目里都会反复用到的那部分底层能力。比如:

  • 公共属性采集
  • 用户属性采集
  • 时间格式和统一时间源
  • 请求数据组装
  • 埋点上报进行抽象
  • 重试规则

另一类是当前我做的 app 项目中的这套事件和字段规则。比如:

  • 当前项目的事件名
  • position / source
  • 扫描链路里的业务字段
  • 页面级埋点规则映射

能放进 SDK 的,是那些别的项目以后还会反复用到的能力,当前项目自己这套事件和字段规则,还是应该留在项目里。

因为这两部分一旦写到一起,这套 SDK 很快就会重新变成“只适合当前项目自己用”的那套实现,不符合跨项目可复用的需求。

真正让我往前推进的,不是“做个 SDK”,而是“让这套能力以后别的项目也可以接入使用”

如果只是站在当前我做的 app 项目里来看,很多事情其实可以做得更省事一点。

比如:

  • Thinking sdk 直接当成前提依赖
  • 请求地址和埋点上报逻辑都直接沿用当前项目这套实现
  • 项目自己的事件规则,继续和通用埋点能力混在一起不拆

从“先让这个项目自己跑起来”的角度看,这些都可以做。

问题在于,我要考虑到“后面别的项目也要复用”这件事,所以不能直接这样搞。

最典型的一点,就是我封装的sdk中要如何使用Thinking sdk。

我可以很明确的一点就是:Thinking sdk可以是一个很重要的属性值的来源,但这套SDK不能一离开它就不能用。

原因很简单,如果SDK最底层默认就和 Thinking sdk死绑在一起,那么以后:

  • 有的项目不想真正依赖 Thinking sdk
  • 有的项目只想用公共属性采集和埋点上报能力
  • 有的项目需要替换底层埋点上报逻辑

这些场景都会变得很难处理。

所以我后面才会往下面这套架构上面去设计:

  • Core
  • ThinkingBridge
  • Transport
  • Adapter

这套架构我接下来会再另外详细展开分享,这篇先不细讲。

SDK 写出来,不等于可以直接替换线上真正使用的埋点上报相关代码

前面那部分,我主要在处理一件事:先把能放进 SDK 的部分,和该留在业务项目里的部分分开。

再往后,问题就变成了:

SDK 就算写出来了,为什么我不能直接替换掉旧的埋点上报相关代码?

事情没有那么简单,我想要在当前项目里,把原来那套埋点上报代码换成新的SDK,还得先确认几件事:

  • 新旧请求数据是不是完全一致
  • 公共属性拿到的值是否准确
  • 时间字段是不是都来自同一个时间
  • 网络出问题,接口请求重试时的传参,到底是不是第一次请求的那些参数

所以再往后,问题就不只是继续补代码了。我真正要处理的是:怎么把线上现在正在用的埋点上报逻辑,稳稳地换成 SDK。

最后我是按下面四步来做的:

  1. 先不要急着替换,先让 SDK 按同样的输入也产出一份请求出来
  2. 再一项一项去对,新请求和旧请求到底差在哪里
  3. 发现动态公共属性会变以后,就把第一次组好的那份数据先固定住,后面发送和重试都用这一份
  4. 等这些地方都确认没问题了,再把真正发请求这件事切到 sdkSendOnly

这周我留下的,不只是代码,还有一套以后还能继续用的方法

现在回头看,这周最有价值的,不只是把代码整理进了 SDK,而是这些问题我都真正做过一遍了:哪些能力适合放进 SDK,旧代码为什么不能直接换,文档和接入方式为什么不能一直拖到最后。

最后留下来的,也不只是项目里一套能跑的埋点代码,而是一套已经在线上验证过、后面别的项目也能接进去用的 SDK。