第3章:SDK架构设计——对象存储SDK的通用分层架构、核心接口抽象、传输协议选择

好,前面我们聊完了加密算法的选型和密钥管理的基本思路。现在该动真格的了——设计一个真正能用的对象存储加密上传下载SDK,它的骨架长什么样?

我见过不少团队,上来就写代码,结果写到一半发现:上传的逻辑和加密的逻辑耦合在一起,换个后端就要重写一半;想加个进度回调,发现无处安放。说白了,就是架构设计没想清楚。一个好的SDK架构,应该像乐高积木一样——各个模块各司其职,互相独立,又能灵活组合。

3.1 通用分层架构:SDK的“三明治”

我个人习惯把SDK分成三层,你也可以理解成“三明治模型”:

  • 接口层(最上层): 对外暴露的API。提供简单、统一的上传、下载、删除、列举等操作。用户只需要关心业务逻辑,不用管底层细节。
  • 核心层(中间层): 逻辑处理中心。负责加密/解密、数据分片、断点续传、重试策略、鉴权签名等核心功能。这是SDK的“大脑”。
  • 传输层(最底层): 对接存储后端。处理HTTP/HTTPS协议、连接池、DNS解析、TLS握手等。说白了,就是怎么把数据发出去和收回来。
关键点: 分层不是摆设,是“解耦”的手段。每一层只依赖下一层提供的契约,不越级调用。这样,哪天你想把HTTP换成gRPC,或者把本地存储换成阿里云OSS,只需要替换最底层那一层,上层代码几乎不用动。

我在项目中见过很多团队,直接拿一个开源的HTTP SDK(比如axios、okhttp)当底层,然后在业务代码里直接调用这个SDK,中间夹揉了一大堆加密和鉴权逻辑。结果就是,换一个后端存储服务,代码根本没法复用,整个推倒重来。嗯,这就是典型的“分层没做好”。

3.2 核心接口抽象:多路复用与统一桩

接口层的设计至关重要。你不能给用户暴露一个“大而全”的类,里面上百个方法,用户看了头皮发麻。好的接口抽象,应该是“小而美”的。

我推荐使用多路复用统一桩的模式:

  • 定义三个基础接口:ObjectStorage(存储操作)、EncryptionEngine(加密操作)、TransferManager(传输管理)。
  • ObjectStorage 接口提供 upload()、download()、delete()、list() 等业务方法。
  • EncryptionEngine 接口提供 encrypt()、decrypt()、keyEnvelope() 等方法。
  • TransferManager 接口负责处理并发、重试、进度回调。

这三个接口的组合,就是SDK的“统一桩”。用户通过依赖注入,可以自由组合不同的实现。

// 例子:核心接口抽象(Java风格伪代码)
public interface ObjectStorage {
    AdapterResponse upload(String bucketName, String key, InputStream data, ObjectMetadata metadata);
    AdapterResponse download(String bucketName, String key, OutputStream output);
    AdapterResponse delete(String bucketName, String key);
    AdapterResponse list(String bucketName, String prefix);
}

public interface EncryptionEngine {
    CipherResult encrypt(byte[] plaintext, EncryptionContext context);
    byte[] decrypt(byte[] ciphertext, EncryptionContext context);
    KeyEnvelope getKeyEnvelope(String keyId);
}

public interface TransferManager {
    void setMaxConnections(int max);
    void setRetryStrategy(RetryStrategy strategy);
    CompletableFuture<ProgressResponse> uploadWithProgress(ObjectStorage storage, String bucket, String key, InputStream data);
    CompletableFuture<ProgressResponse> downloadWithProgress(ObjectStorage storage, String bucket, String key, OutputStream output);
}

你想想看,TransferManager 这个接口的存在,其实是为了把“控制并发和重试”这件脏累活从业务代码里剥离出去。这样,业务方写代码时,只需要关心“我要上传这个文件”,具体怎么分片、怎么重试,SDK帮你搞定。

3.3 传输协议选择:HTTP/1.1 vs HTTP/2 vs gRPC

底层传输协议该怎么选?这是个好问题。很多人觉得“HTTPS就够了吧”。确实,对于大多数场景,HTTPS+HTTP/1.1已经足够。但有几个点我建议你心里有数:

协议 优势 劣势 适用场景
HTTP/1.1 生态成熟,代理友好,调试方便 队头阻塞,连接利用率低 小文件上传、低频操作、调试环境
HTTP/2 多路复用,二进制帧,头部压缩 单连接并发高时仍可能卡住 大文件分片上传、高并发场景
gRPC (HTTP/2) 强类型接口,双向流,内置解压缩 调试困难,代理支持差,对服务端要求高 流式加密传输、服务端编解码、C/S架构
我的经验: 对于大多数公有云对象存储(AWS S3、阿里云OSS),底层都使用HTTP/1.1。因为它们是RESTful接口,千亿级别的请求量,协议栈稳定是第一位的。如果你的SDK是面向私有化部署、或者自建对象存储系统,可以考虑HTTP/2来提升并发性能。

还有一个值得注意的点:加密传输。HTTPS(TLS)是必须的,这是底线。但在一些定制化场景,比如你内部网络环境可控,又想追求极致性能,可以探讨“TLS卸载”或“加密通道复用”。不过,我建议你别为了那点性能牺牲安全性。我在一个项目里见过,为了省一次TLS握手,团队让客户端和服务端之间明文传输加密后的数据块。结果一次内网攻击就漏了数据。嗯,捡了芝麻丢了西瓜。

3.4 核心传输流程:一件完整的事

好了,有了分层和接口,我们来看一个完整的上传流程,你就明白这些层是怎么配合的了:

  1. 用户调用 upload(): 接口层接收业务参数。
  2. 核心层进行数据预处理: 分片(如果文件大)、读取密钥、对数据块调用 EncryptionEngine.encrypt() 进行加密。
  3. 核心层生成描述文件(元数据): 记录加密算法、密钥ID、初始向量(IV)等信息,和加密后的数据块一起交给传输层。
  4. 传输层发起HTTP请求: 把加密数据块和元数据通过PUT请求发送到存储后端。
  5. 结果回调: 成功或失败,通过接口层返回给用户。

你看,每一层做的事情都很纯粹。换个存储后端,我只需要替换传输层的实现;换个加密算法,我只需要替换核心层的EncryptionEngine实现。

3.5 层次化架构图

对象存储加密SDK四层架构 接口层 ObjectStorage (upload/download/delete/list) 面向业务方,提供简洁统一的API入口 核心层 EncryptionEngine (encrypt/decrypt/keyEnvelope) TransferManager (并发控制/重试策略/进度回调) 数据分片、密钥管理、元数据组装 传输层 HTTP/HTTPS, 连接池, DNS解析, TLS握手 对接具体存储后端(S3/OSS本地文件系统等) 存储对象层 对象 (Object) + 元数据 (Metadata) + 加密块 注意:接口层不直接触碰到传输层或存储层;核心层负责加密逻辑后把加密数据交给传输层,传输层只负责发送,不关心数据内容

你看这张架构图,从上到下,职责非常清晰。接口层对用户说话,核心层对逻辑说话,传输层对接后端,存储层则是对应的后端实体。每层之间用明确的契约(接口)隔开,互不打扰。

我曾经踩过的坑: 一开始我的SDK设计,核心层和传输层混在一起。比如上传一个文件,过程里跑了三次网络请求:第一次获取分片ID,第二次上传分片,第三次提交分片。这三个动作都被写在一个方法里,逻辑和网络调用紧耦合。后来我想换一个传输实现(从闭包改成了okhttp),结果发现核心层几乎要全部重写。从那以后,我坚决要求传输层的接口必须是幂等且无状态的,核心层只关心“发什么”,不关心“怎么发”。

3.6 总结与关键要点

这一章的内容比较硬核,因为涉及到SDK的骨架设计。我建议你记住这几个关键点:

  • 分层是奢侈品,但也是必需品。 不做分层,一开始开发很快,但维护成本会指数级上升。
  • 接口抽象是SDK的“契约”。 定好ObjectStorage、EncryptionEngine、TransferManager这三个核心接口,后面的所有实现都是沿着这个契约走。
  • 传输协议的选择因地制宜。 大部分情况HTTP/1.1+HTTPS就够用了。不要为了炫技选择gRPC,除非你真的需要双向流或强类型约束。
  • 面向扩展开放,面向修改关闭。 当你需要增加一个新的存储后端,或者更换加密算法,你不需要去修改SDK的框架代码,只需要新增一个实现类。