4. 模块拆分原则(上):高内聚低耦合、单一职责、通用闭包原则
模块拆分是业务系统架构设计的核心决策点。错误的拆分会导致后续迭代中频繁的跨模块重构、接口爆炸和部署耦合。本节聚焦三个最基础且最实用的拆分原则:高内聚低耦合、单一职责和通用闭包原则。这三个原则分别从内聚性、职责边界和变更影响面三个维度指导模块边界的划定。
4.1 高内聚与低耦合:模块拆分的黄金法则
高内聚(High Cohesion)要求模块内部的元素(类、函数、数据)紧密关联,共同完成一个明确的功能目标。低耦合(Low Coupling)要求模块之间的依赖关系尽可能弱,接口尽可能简洁。两者共同决定了系统的可维护性和可测试性。
| 维度 | 高内聚表现 | 低耦合表现 |
|---|---|---|
| 修改影响 | 修改一个功能只需改动一个模块 | 修改一个模块不强制修改其他模块 |
| 复用性 | 模块可独立抽取到其他系统 | 依赖接口而非具体实现 |
| 测试成本 | 单元测试覆盖率高,Mock 少 | 集成测试场景简单,Stub 少 |
| 典型反例 | 一个模块同时处理订单和库存 | 模块间通过共享数据库表直接访问 |
4.2 单一职责原则(SRP)在模块级别的应用
单一职责原则(Single Responsibility Principle, SRP)在模块级别意味着:一个模块应该只有一个引起它变化的原因。这里的“职责”不是指“只做一件事”,而是指“只对一类利益相关者负责”。
在业务系统中,常见的职责分类维度包括:
- 按业务领域:订单、支付、库存、用户——每个领域一个模块。
- 按变更频率:核心稳定逻辑与频繁变动的规则策略分离。
- 按技术层次:业务逻辑、数据访问、外部集成——分层职责。
- 按数据所有权:谁拥有数据,谁负责数据的读写和校验。
4.3 通用闭包原则(CCP):按变更频率聚合
通用闭包原则(Common Closure Principle, CCP)指出:应该将那些因相同原因、在同一时间需要修改的类或组件聚合到同一个模块中。CCP 是 SRP 在模块级别的逆向表达——SRP 强调“不要将不同原因的变化放在一起”,CCP 强调“将相同原因的变化放在一起”。
CCP 的核心价值在于减少跨模块的变更传播。当业务规则发生变化时,如果相关代码分散在多个模块中,就需要同时修改多个模块并协调发布,显著增加部署风险。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());
}
}
}