统一加密API架构设计:分层思想与模块化
做统一加密API的时候,我最头疼的问题就是「一把抓」——加密算法、数据库存储、密钥管理全揉在一起,改一个地方崩一片。后来我悟了:分层不是炫技,是让复杂系统能活下来。这一章我就把压箱底的分层经验拆开讲,你看完就能直接拿去搭项目。
1. 分层架构的本质(我的理解)
说白了,分层就是把「做什么」「怎么做」「用什么做」拆成三件独立的事。举个例子:接口层只管「用户要加密AES」,核心层只管「用AES-256-GCM算一下」,适配层只管「把结果存到MySQL还是Redis」。各层之间只认接口,不认实现。
我刚开始做的时候,在核心层里直接写了MySQL查询语句。结果后来要切到PostgreSQL…那叫一个酸爽。从那以后,我强制每个层只依赖下层抽象,绝不下潜两层。
2. 四层架构总览
我们的统一加密API分了四层,每一层都有自己的封地:
| 层级 | 核心职责 | 典型模块 | 禁止行为 |
|---|---|---|---|
| 接口层 | 暴露统一API,参数校验、鉴权 | REST/gRPC路由、DTO转换 | 直接调用数据库 |
| 核心层 | 加密/解密/哈希算法调度 | AES、SM4、RSA、SHA256 | 依赖具体数据库实现 |
| 适配层 | 数据库差异抹平,统一CRUD | MySQLAdapter、RedisAdapter | 包含业务逻辑 |
| 工具层 | 密钥管理、配置解析、异常规范 | 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去存缓存;循环依赖就是适配层调用核心层,核心层又回调适配层。这俩一出,你的架构跟意大利面条一样。
- 强制规则:每层只依赖下一层的抽象接口,具体实现通过依赖注入传入。
- 代码审查:审查时如果看到import中有跨两层以上的包,直接标注「架构风险」。
- 模块化拆分:如果某个功能必须跨层(比如审计日志),可以考虑在工具层定义接口,各层实现并通过事件总线触发。
我去年重构一个遗留系统时,发现核心层和适配层之间出现了环形依赖——核心层为了获取密钥缓存,直接调了适配层的缓存实现;适配层为了记录审计日志,又调了核心层的加密函数。最终我花了整整两周,把所有跨层调用改成了抽象接口注入,才把死结解开。所以别偷懒,分层要守规矩。
好了,这一章我们把架构骨架立起来了。下一层一层往下挖的时候,你就会有清晰的地图——知道某个东西该放哪,不该放哪。