这一周我接到一个需求:领导要求我把现在做的 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
- 有的项目只想用公共属性采集和埋点上报能力
- 有的项目需要替换底层埋点上报逻辑
这些场景都会变得很难处理。
所以我后面才会往下面这套架构上面去设计:
CoreThinkingBridgeTransportAdapter
这套架构我接下来会再另外详细展开分享,这篇先不细讲。
SDK 写出来,不等于可以直接替换线上真正使用的埋点上报相关代码
前面那部分,我主要在处理一件事:先把能放进 SDK 的部分,和该留在业务项目里的部分分开。
再往后,问题就变成了:
SDK 就算写出来了,为什么我不能直接替换掉旧的埋点上报相关代码?
事情没有那么简单,我想要在当前项目里,把原来那套埋点上报代码换成新的SDK,还得先确认几件事:
- 新旧请求数据是不是完全一致
- 公共属性拿到的值是否准确
- 时间字段是不是都来自同一个时间
- 网络出问题,接口请求重试时的传参,到底是不是第一次请求的那些参数
所以再往后,问题就不只是继续补代码了。我真正要处理的是:怎么把线上现在正在用的埋点上报逻辑,稳稳地换成 SDK。
最后我是按下面四步来做的:
- 先不要急着替换,先让 SDK 按同样的输入也产出一份请求出来
- 再一项一项去对,新请求和旧请求到底差在哪里
- 发现动态公共属性会变以后,就把第一次组好的那份数据先固定住,后面发送和重试都用这一份
- 等这些地方都确认没问题了,再把真正发请求这件事切到
sdkSendOnly
这周我留下的,不只是代码,还有一套以后还能继续用的方法
现在回头看,这周最有价值的,不只是把代码整理进了 SDK,而是这些问题我都真正做过一遍了:哪些能力适合放进 SDK,旧代码为什么不能直接换,文档和接入方式为什么不能一直拖到最后。
最后留下来的,也不只是项目里一套能跑的埋点代码,而是一套已经在线上验证过、后面别的项目也能接进去用的 SDK。