Appearance
AstralVR SDK 架构设计
从 0 到 1 主导面向开发者的 AstralVR SDK 核心架构设计,运用面向接口编程与表驱动法,模块耦合度降低约 40%。
模块耦合度↓ 约 40%
功能模块6 个
扩展成本1 行注册
背景
亿真网络在做元宇宙 UGC 平台 AstralVR。平台的内容供给依赖第三方开发者:他们需要一个 SDK,把自己在 Unity 里做的 3D 内容打包、统计、压缩并上传到平台。我作为 U3D 组长负责这套 SDK 的核心架构设计。
SDK 的功能范围:
| 模块 | 功能 |
|---|---|
| 鉴权与登录 | 用户登录、Token 管理 |
| 资源界面 | IMGUI 实现的登录、资源、描述介绍、上传界面 |
| 资源打包 | 场景 + 资源的 AB 打包 |
| 数据统计 | 场景面数、材质数、贴图大小等数据采集 |
| 压缩与上传 | ZIP 压缩后上传到腾讯 COS |
| 本地化 | 多语言支持 |
问题
SDK 项目和普通业务代码不一样,它有两个硬约束:
- 模块多且互相独立:鉴权、打包、统计、上传彼此之间没有必然的调用关系,如果写成相互 new 的网状的依赖,任何一个模块的改动都会传导到全 SDK。
- 外部依赖会换:云存储今天是腾讯 COS,平台对接今天是 SpatialOS,明天都可能换。如果业务代码直接 import 第三方 SDK,替换成本是全量回归。
要解决的问题因此不是「把功能写出来」,而是「让功能之间不互相认识,让第三方依赖可以整体替换」。
方案
整体结构
mermaid
flowchart TD
UI["IMGUI 界面层<br>登录 / 资源 / 上传"] --> REG["Service Registry<br>表驱动注册表"]
REG --> AUTH["IAuthService<br>鉴权与 Token"]
REG --> PACK["IPackService<br>AB 打包"]
REG --> STAT["IStatService<br>资源数据统计"]
REG --> UP["IUploadService<br>压缩上传"]
UP --> COS["ICloudStorage<br>腾讯 COS 封装"]
PACK -.不直接依赖.-> COS两条设计原则支撑这个结构:
- 面向接口编程:每个服务先定义接口(
IAuthService/IPackService/IUploadService/IStatService),接口独立实现、互不依赖。调用方只持有接口,不持有实现类。 - 表驱动法:不用 if-else 链分发服务,用字典做注册表。
csharp
// 不用 if-else 链,用 Dictionary 映射
var serviceMap = new Dictionary<string, IService> {
["auth"] = new AuthService(),
["pack"] = new PackService(),
["upload"] = new UploadService(),
};新增服务只需要加一行注册,不修改核心逻辑——这是「开放扩展、关闭修改」最省事的实现方式。
单一职责切分
- SDK 只负责客户端工具链,不包含平台业务逻辑。
- 腾讯 COS 对接独立封装为
ICloudStorage接口,打包模块只依赖接口,不知道 COS 的存在。 - SpatialOS 对接独立封装为
ISpatialService接口,同理。
关键实现
界面层与逻辑层分离。 四个 IMGUI 界面(登录、资源、描述、上传)只做展示和用户输入收集,所有动作都转发给注册表里的服务接口。界面不知道 AuthService 怎么管理 Token,也不知道 UploadService 先压缩还是先统计。
数据统计前置。 打包前由 IStatService 采集面数、材质数、贴图大小,一方面给开发者在上传界面看到资源体检结果,另一方面让平台侧能拒绝超限内容,把问题拦在上传之前。
上传链路串行化。 打包 → ZIP 压缩 → COS 上传是一条单向流水线,每步只接受上一步的产物作为输入,失败时能在明确的环节中断和重试,不会出现「压了一半开始传」的中间态。
登录态收敛在鉴权模块。 Token 的获取、刷新和失效都由 IAuthService 内部管理,上传等需要鉴权的服务通过接口取登录态,各自不保存一份——否则 Token 过期逻辑会散布到每个模块里,改一次漏一片。
本地化独立成模块。 多语言文案不进界面代码,由本地化模块统一供给,四个 IMGUI 界面只取 key。界面加文案和加语言是两次独立改动,互不阻塞。
指标
| 指标 | 结果 |
|---|---|
| 模块耦合度 | 降低约 40% |
| 新增服务成本 | 注册表加一行,核心逻辑零改动 |
| 第三方替换成本 | 替换接口实现即可,业务代码不动 |
SDK 最终支撑 AstralVR 平台上线,接口设计模式也在后续项目中复用。
复盘
- SDK 架构的第一优先级是隔离变化:功能模块之间隔离,第三方依赖更要隔离。接口不值钱,值钱的是「换实现时业务代码一行不改」。
- 表驱动法在小规模项目里经常被嫌「过度设计」,但只要服务数量超过 3 个且会增长,注册表就比 if-else 链便宜。
- 这套「接口 + 注册表 + 第三方封装」的套路,后来在小鹏做 XMapEngine 的 Service 层设计时直接复用了——好的架构经验是可以带走的。