鸿蒙端云信 IMSDK 架构探索
为什么先讲架构
从使用者视角看,IM SDK 往往只是一个统一入口。创建实例之后,登录、消息、会话、用户这些能力依次展开,业务真正关心的是登录能否顺利完成、消息能否稳定收发、会话状态能否持续维护,而不会主动追问这些能力在内部究竟怎样分工。
但真正决定一套 SDK 能否长期演进的,往往不是它能不能先把几个能力做出来,而是这些能力增长之后,系统还能不能继续保持清楚的边界和稳定的组织。对鸿蒙端这套云信 IMSDK 来说,后续的登录、同步、消息、会话和数据库之所以能各自展开,前提正是架构先回答了几类不同的问题:能力边界怎么定义,实例怎样装配和运行,能力怎样被裁剪和替换,持续增长的业务复杂度又该落到哪里。
因此,先不急着进入某个具体功能,而是先回答一个更基础的问题:看起来只是一个统一入口的鸿蒙云信 IMSDK,为什么内部还需要一套清楚的架构分工。只有这个问题先讲清,后面再进入登录、同步、消息、会话和数据库这些专题时,读者才不会把它们看成彼此孤立的几个主题,而能把它们重新放回同一套运行中的架构骨架里。
一、为什么还要拆开
先看一张总图:
这张图最值得注意的,并不是内部存在几个模块,而是它揭示出了一组很现实的张力。业务侧希望拿到的是稳定、统一的能力入口,希望登录、消息、会话这些能力始终以清晰方式出现;而 SDK 内部却注定要不断变化,新能力会加入,不同产品形态会提出不同组合要求,不同环境也会带来额外约束。对外需要稳定,对内又必须变化,这两件事天然不是一回事。
如果没有内部拆分,这种张力很快就会直接落到统一入口上。入口一旦同时承担能力定义、实例组织和业务实现三种职责,新增能力会牵动旧接口,环境差异会扩散到业务逻辑,彼此无关的问题也会在同一处不断堆积。时间一长,外部看起来仍然只有一个 SDK,内部却会逐渐变成一个难以继续生长的单点。
鸿蒙端这套云信 IMSDK 的当前结构,正是围绕这组问题长出来的。它没有把所有事情混在一起,而是把不同性质的问题分开处理:先把能力定义放在一起,再把实例运行放在一起,最后把消息、会话、群组这类业务复杂度放到更适合它们生长的位置。这样做之后,对外依然能保持统一能力面,对内却保留了继续扩展、替换和裁剪的空间。
因此,把能力边界、实例运行和业务域承载放在一起看,更适合把它理解成一种架构处理办法,而不是一组静态划分。它首先解决的,是怎样在保持统一使用形态的同时,让内部结构还能继续演进。
二、能力边界
沿着这个问题继续往下看,最先需要回答的,并不是某个能力怎样实现,而是这套 SDK 到底准备向外提供什么。只有这个问题先被回答,后面的实现才不会反过来不断改变外部世界的形态。
在这套架构里,这件事情首先由 base 来承担。登录、消息、会话、用户、好友、群组、搜索、存储、工具能力以及若干内部协作能力,都会先被组织进同一张能力面之中。对调用方而言,真正被依赖的首先不是某个具体实现,而是这张能力面本身。换句话说,业务侧首先接触到的,不是实现方式,而是能力边界。
这也是为什么 base 不能被简单理解成一个公共工具集合。它更重要的工作,是先把边界固定下来。哪些能力应该被 SDK 正式承诺,哪些对象只是辅助工具,哪些能力虽然存在但只服务于内部运行时,这些问题都需要先在这里说清楚。只有这一层先稳定下来,后续模块的变化才不会不断冲击外部接口。
更进一步看,base 也不只面向外部。登录、消息、会话、同步、数据库等内部协作时需要遵守的规则,同样需要先在这里被说明。这意味着,base 同时承担了两件事:向外给出统一能力面,向内给出模块之间协作的基本约束。也正因为如此,它不是一个普通公共层,而更像是整套架构真正开始站稳的地方。
从这一点再回头看整套架构,就会更容易理解为什么最先被确定的是 base。因为一套需要长期演进的 SDK,最先稳定下来的从来不是实现细节,而是能力边界与协作边界。
三、实例成形
如果说 base 回答的是“这套 SDK 提供什么能力”,那么 nim 回答的就是“这些能力怎样真正变成一个可运行的实例”。
从外部看,业务看到的只是一次实例创建;但从内部看,这对应的是一整套组织过程。实例在真正可用之前,需要先完成基础协议与配置准备、核心能力注册、能力关系校验、缺省能力补位,以及最终的实例接管。它并不是简单地“把若干对象 new 出来”,而是要把不同能力按顺序放到同一个运行环境里,让它们能够在后续持续协作。
这一过程可以用下图理解:
从这里可以看出,nim 最重要的价值不在于它直接承载了多少业务逻辑,而在于它让实例真的“活起来”。消息、会话、群组这些能力最终当然都会运行在这个实例中,但它们并不是天然就能协作的。实例怎样初始化,哪些能力先进入运行状态,内部基础设施怎样准备好,这些问题都需要有一个统一位置来处理,这正是 nim 所承担的职责。
如果进一步抽象,可以把 nim 理解为一套运行中的承载环境。它负责实例创建规则、能力启停状态、内部事件流转以及若干基础设施,并按初始化顺序把这些能力逐步组织起来。也就是说,nim 并不直接展开消息、会话、群组本身的业务复杂度,它承担的是“让这些复杂度能够在同一实例里稳定运转”这件事。
这也有助于澄清一个常见误解:SDK 的统一入口,并不等于统一实现中心。外部看到的是一个实例,内部真正存在的却是一套被组织好的运行环境。入口被统一了,实现并没有因此混成一团。
四、能力补位
在这套结构里,还有一个很容易被忽略、但其实很重要的安排:并不是所有能力都会在任何时候、任何环境下以同样方式启用。某些能力没有被显式装入时,系统会先用默认占位实现把它补上。
这一安排不只是为了“避免报错”,它至少解决了两个更根本的问题。
第一,它让能力裁剪真正变得可行。一个面向多业务场景的 IM SDK,不可能要求所有实例都启用完全相同的一组能力。某些产品只需要登录、消息和会话,某些产品则需要更完整的扩展能力。如果没有占位实现作为缓冲,那么运行时就只能假定每一项能力都必须存在,所谓按需组合也就很难成立。
第二,它让环境差异能够被收拢在运行时内部。某些能力并不是在所有环境下都应该等价启用,当运行前提不满足时,系统不会把这种差异直接暴露给业务调用侧,而是先通过占位实现把差异收住。这样一来,业务侧面对的仍然是统一能力面,而不是一套会随着环境不断变化的调用语义。
这一过程可以用下图理解:
从这张图可以看到,上层始终面对同一张能力面,真正决定“落到真实实现还是落到占位实现”的,是实例内部的组织过程,而不是调用层。也正是因为有了这一层安排,插件化和环境适配才没有直接转化成业务侧的理解负担。
五、复杂度落点
当能力边界已经被界定、实例运行也已经被组织起来之后,IM 真正的复杂度才有了清晰落点。这个落点并不是某一个抽象大模块,而是消息、会话、群组、好友、用户、搜索、信令等不同业务域。
理解这套架构时,真正需要抓住的,不是工程里怎样归类这些能力,而是为什么消息、会话、群组这些业务域都需要各自稳定的复杂度落点。它们看上去同属 IM 能力,实际面对的却是不同类型的问题,因此不能被压进同一个实现中心里。
这种划分方式之所以合理,是因为 IM 系统的复杂度天然就是领域性的,而不是单纯流程性的。消息域要处理发送、接收、回执、撤回、删除、检索和落库;会话域要处理会话列表、最后一条消息、未读数、版本缓存与回填;群组域则要处理群资料、成员、群通知及同步状态。它们之间当然存在协作关系,但并不适合被压缩进同一个实现中心。
因此,这套架构真正有价值的地方,不是把功能切得更散,而是让复杂度能够按业务域沉降,使消息、会话、群组、搜索等能力都能在相对独立的边界内演进、优化和维护。对于一个需要持续迭代的 IM SDK 而言,这一点尤其重要,因为不同业务域的变化节奏、性能瓶颈和可靠性风险本来就不相同。
六、为什么这样更稳
如果进一步追问,为什么是今天这样的内部结构,而不是别的样子,答案其实并不抽象。因为 IM SDK 面临的变化,大致就集中在几类问题上。
第一类变化来自能力面的扩展。最初也许只有登录、消息、会话,随后还会继续扩展到搜索、订阅、话题、AI、统计、透传等能力。如果没有先被稳定下来的能力边界,这些变化很快就会直接冲击外部接口。
第二类变化来自能力组合方式的差异。不同产品形态、不同版本策略、不同部署条件,都会导致实例中启用的能力集合不同。如果没有单独承担实例运行的部分,不同能力之间的互斥、依赖和裁剪关系就只能散落在业务实现中。
第三类变化来自业务域本身的持续演进。消息系统、会话系统、群组系统、搜索系统的内部问题并不相同,迭代节奏也并不一致。如果这些复杂度都回流到统一入口,入口就会越来越重,而边界则会越来越模糊。
也正因为如此,今天看到的拆分方式并不是一种偶然选择,而是在这些变化不断出现之后,逐渐被证明更适合承接它们的方式。能力定义有自己的位置,实例运行有自己的位置,业务复杂度也有自己的位置,系统扩大之后才能继续保持稳定。
七、回到起点
回到这篇文章最初的问题,鸿蒙端云信 IMSDK 之所以形成今天的架构,并不是为了把工程拆成几块,而是因为它必须同时处理三类不同性质的问题:能力边界需要先被定义,实例运行需要被组织起来,持续增长的业务复杂度也需要有稳定落点。今天看到的这套分工,正是在处理这三类问题。
因此,理解这一篇的关键,并不是去记工程里的叫法,而是先建立一个基本认识:对外看似统一的 SDK 入口,背后一定有一套能够持续演进的内部结构。只有在这样的前提下,后续去看登录、同步、消息、会话和数据库,才会更容易理解它们为什么会出现在今天这样的位置上。
下一篇将从实例真正开始运行的时刻切入,继续讨论登录链路与生命周期状态机如何把这套架构连接起来。