统一加密API架构设计:分层思想与模块化

做统一加密API的时候,我最头疼的问题就是「一把抓」——加密算法、数据库存储、密钥管理全揉在一起,改一个地方崩一片。后来我悟了:分层不是炫技,是让复杂系统能活下来。这一章我就把压箱底的分层经验拆开讲,你看完就能直接拿去搭项目。

1. 分层架构的本质(我的理解)

说白了,分层就是把「做什么」「怎么做」「用什么做」拆成三件独立的事。举个例子:接口层只管「用户要加密AES」,核心层只管「用AES-256-GCM算一下」,适配层只管「把结果存到MySQL还是Redis」。各层之间只认接口,不认实现。

我刚开始做的时候,在核心层里直接写了MySQL查询语句。结果后来要切到PostgreSQL…那叫一个酸爽。从那以后,我强制每个层只依赖下层抽象,绝不下潜两层。

2. 四层架构总览

我们的统一加密API分了四层,每一层都有自己的封地:

层级核心职责典型模块禁止行为
接口层暴露统一API,参数校验、鉴权REST/gRPC路由、DTO转换直接调用数据库
核心层加密/解密/哈希算法调度AES、SM4、RSA、SHA256依赖具体数据库实现
适配层数据库差异抹平,统一CRUDMySQLAdapter、RedisAdapter包含业务逻辑
工具层密钥管理、配置解析、异常规范KeyManager、ConfigLoader调用上层模块

注意:层间依赖只能向下,不能出现核心层跳过适配层直接调用工具层的数据库连接——我见过血的教训。

统一加密API四层架构图 统一加密API 四层架构 接口层 (API Layer) RESTful/gRPC路由 DTO转换与参数校验 核心层 (Core Layer) AES/SM4/RSA加密调度 哈希/签名/密钥协商 「只认抽象,不认数据库」 适配层 (Adapter Layer) MySQL Adapter Redis Adapter Mongo Adapter 统一CRUD接口,屏蔽方言差异 工具层 (Utility Layer) 密钥管理 / KeyManager 配置加载 / ConfigLoader 核心原则 依赖只向下 接口只向上 跨层调用 = 架构事故

一句话总结这张图:接口层接待请求,核心层做加密运算,适配层搞定数据库,工具层伺候所有人。箭头方向就是依赖方向,看到反向箭头你就可以骂人了。

3. 接口层(API Layer)

接口层是门面,用户只管调。我只在这里做三件事:

  • 解析HTTP/gRPC请求,校验参数格式和业务权限
  • 把外部DTO转成核心层能理解的内部模型
  • 处理公共异常(比如参数缺失、签名过期)并返回统一错误码

我建议:接口层不要写超过50行的函数。如果有人把数据库查询写在这里,你就把他代码打回去重写——这是纪律问题。

4. 核心层(Core Layer)

这里才是处理加密逻辑的地方。每次有人问我「为什么不用现成库直接跑?」我都举一个例子:假设你需要同时支持AES-256-GCM、SM4-CBC、RSA-OAEP,并且每个算法还要配合不同的密钥派生函数。如果你在业务代码里到处new Cipher(),替换算法的时候不得疯?

我的做法:在核心层定义抽象接口Encryptor和Decryptor,每个算法一个实现,通过策略模式注册到工厂。核心层只关心「加密/解密」这个行为,完全不关心「数据存在哪」。你看看,多么清爽。

// Core Layer 接口示例(Go)
type Encryptor interface {
    Encrypt(plaintext []byte, key Key) ([]byte, error)
    Algorithm() string
}

type KeyManager interface {
    GetKey(algorithm string) (Key, error)
}

嗯,这里要注意:核心层不能包含任何数据库驱动的import,哪怕是配置文件的读取也要交给工具层。我之前发现有人为了性能在核心层里缓存数据库连接——结果换库时乱成一锅粥。

5. 适配层(Adapter Layer)

适配层的存在就是为了让不同数据库看起来一样。你想想看,MySQL的INSERT…ON DUPLICATE KEY UPDATE和Redis的SETEX,语法上八竿子打不着。但通过适配层,我可以统一成Store(key, value, ttl) error。

我在实际项目里用过一种写法:定义StorageAdapter接口,每个数据库写一个独立实现模块。例如:

// Adapter Layer 接口
type StorageAdapter interface {
    Store(ctx context.Context, entry *EncryptionEntry) error
    Get(ctx context.Context, id string) (*EncryptionEntry, error)
    Delete(ctx context.Context, id string) error
}
  • MySQLAdapter:使用SQL构建器,处理ORM差异
  • RedisAdapter:用Hash或String结构存储,处理过期
  • MongoAdapter:利用Document模型,处理嵌套文档

避坑:我曾经在适配层里写了业务逻辑——比如根据用户角色动态选择加密算法。后来发现一换库就得改业务规则,哭都来不及。记住:适配层只做「存」和「取」,业务决策全扔给核心层。

6. 工具层(Utility Layer)

工具层是个「大管家」。密钥轮换、配置热加载、异常码定义、日志切面……这些和具体加密算法、具体数据库都没关系的东西,全塞到这里。为什么?因为如果你把密钥管理放到核心层,那每个算法模块都得带上一份密钥读取逻辑——冗余到你想吐。

工具模块职责使用方式
KeyManager生成、存储、轮换密钥核心层通过接口调用,不关心存储细节
ConfigLoader读取YAML/Env,提供配置快照全局单例,所有层可读(但不可写)
ErrDefs统一错误码与错误结构各层抛出异常时使用,便于前端展示
Logger结构化日志,审计跟踪切面注入,不要每个函数手写

警告!工具层绝不能调用上层模块。我见过有人把KeyManager直接new了一个MySQLAdapter去查密钥——这相当于把适配层细节暴露给了全系统,一旦换库就得改所有地方。正确的做法:KeyManager内部通过StorageAdapter接口(由外部注入)来存取密钥。

7. 避坑指南:跨层依赖与循环依赖

分层最怕两件事:跨两层依赖和循环依赖。跨层依赖就是核心层直接new了MySQLAdapter去存缓存;循环依赖就是适配层调用核心层,核心层又回调适配层。这俩一出,你的架构跟意大利面条一样。

  1. 强制规则:每层只依赖下一层的抽象接口,具体实现通过依赖注入传入。
  2. 代码审查:审查时如果看到import中有跨两层以上的包,直接标注「架构风险」。
  3. 模块化拆分:如果某个功能必须跨层(比如审计日志),可以考虑在工具层定义接口,各层实现并通过事件总线触发。

我去年重构一个遗留系统时,发现核心层和适配层之间出现了环形依赖——核心层为了获取密钥缓存,直接调了适配层的缓存实现;适配层为了记录审计日志,又调了核心层的加密函数。最终我花了整整两周,把所有跨层调用改成了抽象接口注入,才把死结解开。所以别偷懒,分层要守规矩。

好了,这一章我们把架构骨架立起来了。下一层一层往下挖的时候,你就会有清晰的地图——知道某个东西该放哪,不该放哪。