第2章:模块边界定义
2.1 划分原则:高内聚、低耦合与上下文边界
模块边界是安全隔离的第一道防线。边界定义的核心原则包括:
- 业务上下文完整性:同一模块应封装完整的一组业务能力,对外仅暴露必要的接口。
- 变化隔离:将容易变化(技术栈、数据库、第三方依赖)的部分包裹在模块内部,减少变更扩散。
- 依赖层次化:避免循环依赖,模块只能单向依赖更稳定的模块。
- 进程级与库级差异:进程级模块(微服务)通过网络(HTTP/gRPC)通信,插件化模块通过进程内IPC或接口表通信,边界开销和信任模型不同。
经验法则:若两个功能模块需要频繁同步修改,则应合并为一个模块;若只有单向依赖且可异步,则应保持独立。
2.2 接口契约:协议、方法与版本化
接口契约是模块双方达成的通信协议,包含以下要素:
| 维度 | 说明 | 典型约束 |
|---|---|---|
| 通信协议 | HTTP REST、gRPC、消息队列、插件接口表 | 必须明确序列化格式(JSON/Protobuf) |
| 语义契约 | 请求/响应结构、错误码含义、幂等性保证 | 所有错误码需在模块文档中备案 |
| 版本策略 | 前置版本号、后向兼容升级、废弃周期 | 至少保留一个旧版本供过渡期共存 |
| 安全契约 | 身份令牌传递范围、加密层级、重放防护 | 必须原生支持端到端验证 |
关键约束:接口契约必须同时定义“信任假设”——调用方身份如何传递?模块是否信任上游传递的凭据?
2.3 依赖方向:稳定模块向外辐射
依赖方向决定了风险流动路径。正确划分应遵循:
- 基础服务模块(认证、配置、日志)位于依赖底部,被上层模块引用。
- 业务模块只能依赖基础模块,不能反向依赖或跨业务模块直接调用。
- 插件模块对主框架是“注册-回调”关系,插件不可依赖其他插件。
依赖方向混乱会导致安全策略碎片化。例如:若支付模块依赖用户模块的本地缓存,则用户模块的缓存漏洞可直接渗透支付模块。
常见反模式:微服务间双向gRPC调用、插件A直接调用插件B的内部接口、进程级模块共享同一数据库写权限。这些设计都会打破边界信任。
2.4 边界划分实例:微服务与插件化对比
| 维度 | 微服务(进程级) | 插件化(进程内) |
|---|---|---|
| 调用方式 | 网络RPC(HTTP/gRPC) | 方法调用(接口表) |
| 边界隔离粒度 | 独立进程 / 容器 | 类加载器 / 沙箱 |
| 传递 | HTTP Header / gRPC Metadata | 调用上下文(ThreadLocal) |
| 依赖方向 | 单向分层依赖 | 主框架→插件(单向) |
| 安全审计点 | 每次RPC入口 | 插件注册 / 回调检查 |
2.5 模块边界定义总览(SVG结构图)
下图展示了一个典型的多模块系统边界划分,包含微服务A、B、C和插件模块D,箭头表示依赖方向与传递路径。
2.6 边界定义检查清单
在架构设计阶段,逐项确认以下条目:
- 每个模块是否拥有唯一的业务领域(DDD限界上下文)?
- 模块间通信是否只有单向依赖?(允许基础模块无依赖)
- 接口契约是否包含安全要求(传递方式、加密范围)?
- 是否存在跨模块直接读写数据库或共享内存?
- 进程级模块的独立部署是否可验证(每个模块可单独启停)?
落地建议:使用“模块依赖图”工具(如ArchUnit、jqAssistant)自动检查循环依赖,并在CI中集成。插件化系统需强制插件遵循接口版本号,运行时拒绝不兼容的插件。