4. 模块职责与接口契约:模块内聚、接口定义、依赖方向

4.1 核心概念映射

在系统分解中,模块职责决定了“做什么”,接口契约决定了“怎么协作”。二者共同决定了系统的可维护性和演进成本。以下表格归纳了本节将展开的核心关系:

设计维度目标评判标准常见反例
模块内聚模块内部职责单一、关联紧密仅因一个原因修改模块内部代码工具类模块承担业务编排职责
接口契约对外提供稳定、可验证的交互协议接口参数/返回值语义明确,版本兼容接口返回Map任由调用方猜测
依赖方向依赖指向稳定层,避免循环依赖依赖倒置、分层隔离、构建图无环上层模块直接依赖第三方SDK内部类

4.2 模块内聚:从“类”到“上下文”的聚合规则

职责内聚是模块划分的第一原则。实践中,我们通常使用单一职责原则(SRP)判断模块粒度:一个模块应当只有一个引起变化的原因。

  • 功能内聚:同一模块内的所有元素共同完成一个单一功能(如:模块“SHA256计算器”仅输出给定输入的哈希值)。
  • 通信内聚:模块内元素操作同一数据集合(如:模块“用户令牌管理”读取、签发、吊销同一用户令牌数据)。
  • 时序内聚:模块内元素按固定顺序执行(如:初始化模块需依次执行配置加载→资源池预热→健康检查)。
原型落地匹配要点: 构建原型时,先用“文件/目录”作为内聚物理边界。例如,将订单生成、订单验证、订单持久化放在同一目录下,而非按“工具类”跨目录聚合。原型阶段过度抽离会导致后续拆分成本升高。

4.3 接口契约:语义、格式、版本与错误处理

接口契约是模块之间唯一的信任节点。契约应包含以下要素:

  1. 语义清晰的动作名称(动词+名词,如 submitOrder 而非 process)。
  2. 请求/响应的数据结构定义(强类型对象,拒绝Map泛型)。
  3. 幂等性与超时规则(重试策略应在契约中注明)。
  4. 错误码与异常范围(接收方必须处理的错误类型)。
方案选型权衡: 原型阶段接口定义可使用 Protobuf / Thrift IDL 或 OpenAPI 3.0。若团队尚未定技术栈,推荐先用 YAML 手写契约文件,避免过早绑定 RPC 框架。
# 契约定义示例 (order-service / proto / v1 / order_api.proto)
service OrderService {
  // 提交订单(幂等:使用 client_order_id 去重)
  rpc SubmitOrder (SubmitOrderRequest) returns (SubmitOrderResponse);

  // 查询订单状态
  rpc GetOrderStatus (GetOrderStatusRequest) returns (GetOrderStatusResponse);
}

message SubmitOrderRequest {
  string client_order_id = 1;   // 客户端幂等ID
  string user_id = 2;
  repeated Item items = 3;
}

message SubmitOrderResponse {
  string order_id = 1;          // 生成的订单ID
  OrderStatus status = 2;       // PENDING / CONFIRMED / FAILED
  string fail_reason = 3;       // 失败时的详细原因
}

4.4 依赖方向:分层、反转与规避循环

模块之间的依赖方向决定了系统的弹性。依赖规则:高层的策略模块不应依赖低层的实现细节,而是依赖抽象接口。层级依赖方向应始终指向稳定的基础模块。

  • 正向依赖:包A依赖包B(B更稳定)。
  • 反向控制(依赖倒置):接口定义在调用方包内,实现方依赖该接口。
  • 禁止:包A依赖包B,包B又依赖包A(循环依赖需通过引入抽象层或事件总线破除)。

以下SVG图展示了模块职责划分与接口契约在典型分层架构中的依赖关系和信任链路:

模块职责与接口契约:分层依赖与信任链路 表现层 (Presentation) 职责:请求路由、参数校验、会话管理 接口契约:OpenAPI 3.0 / gRPC-Gateway / RESTful 应用/编排层 (Application) 职责:业务流程编排、事务边界 接口契约:应用服务接口(Local/DTO) 领域/业务模块 (Domain) 职责:核心领域逻辑、聚合、领域事件 接口契约:领域服务接口 + 领域事件定义 基础设施 (Infrastructure) 职责:DB访问、缓存、外部服务适配 接口契约:仓库接口(Repository)/ 防腐层 信任链路与依赖反转示例 • 领域层定义 Repository 接口 • 基础设施层实现该接口 • 应用层只能面向领域接口编程 • 表现层不直接依赖基础设施 • 所有跨模块调用必须通过接口契约 • 依赖方向:表现层→应用层→领域层←基础设施层 模块内聚规则: 每次修改只影响一个模块,模块内代码因同一职责内聚

4.5 关键风险点与落地步骤

关键风险点:
  • 接口膨胀:原型期未收敛接口,出现大量类似方法(如 createUser_v1 / createUser_v2),应在一开始就明确契约版本策略。
  • 循环依赖:跨模块引用时未做接口抽离,导致构建失败或运行时死锁。使用构建工具(如 Java 的 jdeps)提前扫描。
  • 内聚不足:模块内同时包含业务逻辑与数据库连接、日志打印等横切关注点,导致模块对外暴露过多细节。

落地步骤(原型阶段可执行):

  1. 绘制模块依赖图(手绘或工具),标记每个模块的输入输出接口。
  2. 在每个模块目录下创建 api/ 子目录,存放接口定义文件(Proto / OpenAPI / 接口抽象类)。
  3. 使用 依赖方向检查脚本 在 CI 中阻止反向依赖。
# 依赖检查示例(基于 Go 的 import 路径检查)
#!/bin/bash
# 检查 domain 层不应 import infrastructure 层
for pkg in $(find ./domain -type d); do
  if grep -q "infrastructure" "$pkg/*.go" 2>/dev/null; then
    echo "ERROR: Domain package imports infrastructure: $pkg"
    exit 1
  fi
done
echo "依赖方向检查通过"

4.6 原型实现匹配要点

  • 在原型代码中保持显式接口定义,禁止模块间通过全局变量或单例直接耦合。
  • 使用依赖注入方式组装模块,便于后续替换实现(如从内存存储切换为 MySQL)。
  • 每个接口的单元测试覆盖率 > 90%,测试即契约文档。

4.7 验收标准

编号验收项检查方式
4-1所有模块职责可在1句话内描述清晰代码评审
4-2模块间接口均为显式定义(非Map/JSON)静态分析 + 审阅
4-3模块依赖图无环(可在构建时验证)CI 依赖检测脚本
4-4任意模块可被Mock替换进行单元测试UT 覆盖率报告
4-5接口契约文档(OpenAPI / Proto 文件)与实现始终保持一致diff 检查 + 契约测试