3. 模块边界定义
模块边界是权限验证的第一道防线。在 Go 业务系统中,无论采用单体架构还是微服务架构,都必须清晰界定“哪里是边界、边界内外如何通信、权限检查落在哪个位置”。本章从概念出发,分别讨论微服务与单体两种架构下的边界划分策略。
3.1 什么是业务模块边界
业务模块边界是指一个独立业务单元对外暴露的、可被其他模块调用的接口集合,以及该单元内部实现细节的封装范围。边界由以下三个要素共同定义:
| 要素 | 说明 | Go 中的典型体现 |
|---|---|---|
| 接口契约 | 模块对外提供的服务签名、输入输出结构、错误约定 | protobuf 定义、interface{} 类型 |
| 数据所有权 | 模块拥有且仅拥有其核心数据的写权限 | 私有 struct + 公开 Getter/Setter |
| 依赖方向 | 依赖只能从外层指向内层,禁止循环依赖 | 依赖注入、internal 包隔离 |
边界即安全域:在权限验证语境下,每个模块边界都是一个安全域。跨域调用必须经过身份认证与授权检查,域内调用可信任(或降低检查粒度)。
下图展示了一个典型 Go 业务模块的边界结构:
3.2 微服务架构下的边界划分
微服务架构将系统拆分为多个独立部署的服务,每个服务拥有明确的网络边界。边界划分遵循以下原则:
- 按业务能力拆分:每个服务对应一个完整的业务子域(如用户服务、订单服务、支付服务)。
- 数据独立:每个服务拥有自己的数据库实例或 Schema,禁止跨服务直接访问数据库。
- 通信标准化:服务间通过 gRPC 或 RESTful API 通信,所有请求必须携带身份令牌。
- 网关统一鉴权:API 网关负责第一层认证,服务内部再做细粒度授权。
| 维度 | 单体应用 | 微服务 |
|---|---|---|
| 边界类型 | 逻辑边界(包/模块) | 物理边界(进程/网络) |
| 通信方式 | 函数调用 | RPC / 消息队列 |
| 权限检查位置 | 中间件 + 函数入口 | 网关 + 服务间 mTLS + 业务层 |
| 数据隔离 | 共享数据库 | 独立数据库 |
| 部署单元 | 单一进程 | 多进程/容器 |
下图展示微服务架构中多个服务之间的边界划分与权限验证关系:
微服务权限验证要点:
- 网关层验证 JWT 有效性,提取用户身份与角色。
- 服务间调用使用 mTLS 或服务账号 Token 保证通信安全。
- 每个服务在入口处再次校验调用方是否具备目标资源的操作权限。
- 避免在服务间传递明文密码或敏感凭证。
3.3 单体应用中的逻辑边界
单体应用虽然部署在单一进程中,但内部仍然需要清晰的逻辑边界。Go 语言通过包(package)和 internal 目录来强制隔离。常见的分层边界模式如下:
| 层级 | 包名示例 | 职责 | 权限检查 |
|---|---|---|---|
| 传输层 | transport/http |
HTTP 路由、请求解析、响应渲染 | 全局中间件(认证) |
| 控制层 | controller |
参数校验、调用 Service、组装响应 | 方法级授权 |
| 业务层 | service |
核心业务逻辑、事务管理 | 资源级权限 |
| 数据层 | repository |
数据库操作、缓存访问 | 不检查(信任上层) |
单体应用中的逻辑边界划分图:
单体架构常见边界陷阱:
- 循环依赖:Service A 调用 Service B,Service B 又反向调用 Service A。应通过引入接口层或事件总线解耦。
- 贫血模型:所有业务逻辑写在 Service 中,Model 只做数据容器。应把内聚的业务行为放到领域对象中。
- 权限检查遗漏:只在 Controller 层做权限检查,但 Service 内部互相调用时跳过检查。建议在 Service 入口处也做资源级校验。
3.4 边界划分与权限验证的映射关系
无论采用何种架构,权限验证都应部署在边界的关键节点上。下表总结了不同边界类型对应的权限验证策略:
| 边界类型 | 验证位置 | 验证粒度 | Go 实现示例 |
|---|---|---|---|
| 网络边界(微服务) | API 网关 | 服务级(是否允许访问该服务) | gin-jwt 中间件 |
| 服务间边界 | gRPC 拦截器 | 方法级(是否允许调用该 RPC) | grpc.UnaryServerInterceptor |
| 模块逻辑边界(单体) | Controller / Service 入口 | 资源级(是否允许操作该数据) | 自定义 Authorize() 函数 |
| 数据访问边界 | Repository 层 | 行级(数据归属校验) | SQL 中嵌入 WHERE user_id = ? |
最佳实践:在 Go 项目中,建议将权限验证逻辑封装为可复用的中间件或装饰器,通过依赖注入挂载到每个边界入口。避免在业务代码中散落权限判断,保持关注点分离。