4. 模块拆分原则(上):高内聚低耦合、单一职责、通用闭包原则

模块拆分是业务系统架构设计的核心决策点。错误的拆分会导致后续迭代中频繁的跨模块重构、接口爆炸和部署耦合。本节聚焦三个最基础且最实用的拆分原则:高内聚低耦合、单一职责和通用闭包原则。这三个原则分别从内聚性、职责边界和变更影响面三个维度指导模块边界的划定。

4.1 高内聚与低耦合:模块拆分的黄金法则

高内聚(High Cohesion)要求模块内部的元素(类、函数、数据)紧密关联,共同完成一个明确的功能目标。低耦合(Low Coupling)要求模块之间的依赖关系尽可能弱,接口尽可能简洁。两者共同决定了系统的可维护性和可测试性。

维度 高内聚表现 低耦合表现
修改影响 修改一个功能只需改动一个模块 修改一个模块不强制修改其他模块
复用性 模块可独立抽取到其他系统 依赖接口而非具体实现
测试成本 单元测试覆盖率高,Mock 少 集成测试场景简单,Stub 少
典型反例 一个模块同时处理订单和库存 模块间通过共享数据库表直接访问
实践判断标准:如果一次需求变更需要同时修改 3 个以上模块,且这些模块属于同一业务领域,说明内聚性不足或耦合度过高,应考虑合并或重新划分模块边界。

4.2 单一职责原则(SRP)在模块级别的应用

单一职责原则(Single Responsibility Principle, SRP)在模块级别意味着:一个模块应该只有一个引起它变化的原因。这里的“职责”不是指“只做一件事”,而是指“只对一类利益相关者负责”。

在业务系统中,常见的职责分类维度包括:

  • 按业务领域:订单、支付、库存、用户——每个领域一个模块。
  • 按变更频率:核心稳定逻辑与频繁变动的规则策略分离。
  • 按技术层次:业务逻辑、数据访问、外部集成——分层职责。
  • 按数据所有权:谁拥有数据,谁负责数据的读写和校验。
常见误区:将 SRP 理解为“一个模块只包含一个类或一个函数”。在模块级别,SRP 允许模块内部包含多个类,只要这些类共同服务于同一个业务目标或同一个变更来源。例如,订单模块可以包含 Order、OrderItem、OrderRepository 等多个类,但它们都因“订单业务规则变化”而改变。

4.3 通用闭包原则(CCP):按变更频率聚合

通用闭包原则(Common Closure Principle, CCP)指出:应该将那些因相同原因、在同一时间需要修改的类或组件聚合到同一个模块中。CCP 是 SRP 在模块级别的逆向表达——SRP 强调“不要将不同原因的变化放在一起”,CCP 强调“将相同原因的变化放在一起”。

CCP 的核心价值在于减少跨模块的变更传播。当业务规则发生变化时,如果相关代码分散在多个模块中,就需要同时修改多个模块并协调发布,显著增加部署风险。CCP 指导我们将这些代码聚合,使变更局限在一个模块内部。

模块拆分原则对比:SRP vs CCP 单一职责原则(SRP) 按“利益相关者”划分 订单模块 支付模块 库存模块 用户模块 每个模块只对一类业务角色负责 订单、支付、库存、用户各自独立 变更原因:业务领域规则变化 通用闭包原则(CCP) 按“变更原因”聚合 促销规则模块 折扣计算 满减策略 优惠券核销 活动时间控制 因“促销规则变更”而需要修改的代码全部聚合在一起 变更原因:营销策略调整 对比

4.4 三个原则的协同与权衡

在实际架构设计中,SRP 和 CCP 往往需要协同使用。SRP 提供模块划分的“业务语义”依据,CCP 提供“变更管理”依据。高内聚低耦合则是衡量划分结果的质量标准。

以下是一个典型的权衡场景:

  • 按 SRP 划分:订单模块、支付模块、库存模块各自独立。优点是职责清晰,缺点是当“下单流程”变更时,可能需要同时修改三个模块。
  • 按 CCP 聚合:将下单流程涉及的所有代码(订单创建、支付扣款、库存扣减)聚合到一个“下单模块”。优点是变更局部化,缺点是模块职责变宽,复用性下降。
  • 折中方案:保持 SRP 的领域划分,但在每个模块内部通过防腐层(Anti-Corruption Layer)和领域事件解耦流程编排,使跨模块变更时只需修改编排层,而不需要深入修改每个模块的内部逻辑。
架构决策清单:当你在模块边界划分上犹豫不决时,依次回答以下问题:
1. 这些代码是否因同一个业务角色(如运营、财务、客服)的需求而变更?→ 是则按 SRP 聚合。
2. 这些代码是否因同一个业务事件(如“大促活动上线”)而同时变更?→ 是则按 CCP 聚合。
3. 如果两个答案都是“是”,优先按 CCP 聚合,因为变更频率是更紧迫的约束。
4. 如果两个答案都是“否”,检查是否应该拆分为更小的模块。

4.5 实战案例:电商订单模块的边界争议

某电商团队在重构时,对“订单模块”的边界产生分歧:

方案 包含内容 优点 缺点
方案A(窄边界) 仅订单 CRUD、状态机 职责单一,易于测试 下单流程需跨模块调用支付和库存
方案B(宽边界) 订单 + 支付对接 + 库存扣减 下单流程变更局部化 模块职责过重,支付和库存逻辑难以复用
方案C(事件驱动) 订单模块 + 领域事件发布 兼顾 SRP 和 CCP 需要事件基础设施,调试复杂度增加

最终团队选择方案C:订单模块保持窄边界,但在下单完成后发布 OrderCreated 领域事件;支付模块和库存模块各自订阅事件并执行后续操作。这样既保持了模块的单一职责,又通过事件机制将“下单流程”的变更影响控制在事件编排层,实现了高内聚低耦合的平衡。

// 订单模块:发布领域事件
public class OrderService {
    private final EventPublisher eventPublisher;

    public Order createOrder(CreateOrderRequest request) {
        Order order = new Order(request);
        // ... 订单校验与持久化
        eventPublisher.publish(new OrderCreatedEvent(order.getId(), order.getItems()));
        return order;
    }
}

// 库存模块:订阅事件
public class InventoryEventHandler {
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        for (OrderItem item : event.getItems()) {
            inventoryService.deductStock(item.getSkuId(), item.getQuantity());
        }
    }
}
关键启示:模块拆分不是一次性的“切分”动作,而是持续演化的过程。在系统初期,可以适当放宽边界(偏向 CCP),随着业务稳定再逐步细化(偏向 SRP)。每次重构时,重新审视模块的内聚性和耦合度,比追求“完美划分”更有实际价值。