模块化单体
模块化单体在一个部署单元内划分业务模块。本文讲解模块接口、依赖约束和事务内外的事件。
1. 面试核心
先记住这几条,再读正文- 部署与模块边界。 模块化单体共享部署单元,各模块只公开业务接口。卡 · 综合题
- 依赖与数据所有权。 单向依赖和唯一数据写入方构成模块边界。
- 结构校验。 Spring Modulith 的结构验证可检查依赖环与内部包越界。
- 事件可靠性。 普通内存事件会随进程退出丢失,持久事件登记支持恢复。卡 · 事务事件与持久登记
2. 业务模块
按业务能力分组后,一个订单需求集中在订单模块。模块内部再安排应用、领域和基础设施代码。
com.example.shop
├── order
│ ├── OrderManagement.java
│ └── internal
├── inventory
│ ├── InventoryManagement.java
│ └── internal
└── payment
├── PaymentManagement.java
└── internal
模块分别拥有规则、接口和内部实现。它们共享进程与发布流水线。
3. 模块接口
公开 API 表达业务能力,例如 reserveInventory。Repository 和持久化实体留在模块内部。公开类型越少,需要维护的外部契约越少。
Java 的 package-private 只约束同一个包。子包也有独立的访问边界。public 类型放在 internal 包中,仍需结构测试约束访问。
Spring Modulith 2.1 默认以根包的直接子包识别模块。模块根包中的公开类型作为 API。额外公开范围通过 named interface 声明。
4. 单向依赖
A 调用 B,B 又引用 A,会形成依赖环。双方的测试、初始化和后续拆分随之耦合。
共同规则应归属实际负责的模块。稳定契约可单独抽出,后续动作可改用事件。共享包应保持小而稳定,避免吸收双方内部实现。
5. 同步调用与事件
用例需要立即判断结果时,使用同步模块 API。只需通知后续工作时,可发布事件。事件处理时机决定事务关系。
Spring 默认同步分发应用事件。监听器异常可影响发布调用与原事务。提交后异步监听则有独立的失败窗口。
5.1 事务事件与持久登记
事务提交后,进程可在监听器执行前退出。普通异步事件保存在内存,重启后缺少恢复依据。
Spring Modulith 2.1 提供 Event Publication Registry。配置持久化实现后,发布记录随业务事务写入。每个事务监听器对应一条处理记录。监听器成功后,登记项标记完成。
失败记录保留,可通过重发机制恢复。监听器仍需幂等,以处理执行成功后确认丢失。跨进程交付的机制见Transactional Outbox 与 CDC。
6. 数据所有权
模块可以共用数据库实例。每张业务表仍由一个模块写入。其他模块通过 API 或只读投影获取数据。
schema、表名前缀和迁移脚本可以标记责任。跨模块事务可维护确需原子提交的规则。此类事务形成显式依赖,应随边界一起评审。
7. 自动化结构检查
结构规则应进入 CI。以下测试检查模块依赖:
class ModuleStructureTests {
@Test
void verifiesModules() {
ApplicationModules.of(ShopApplication.class).verify();
}
}
Spring Modulith 2.1 检查依赖环与内部包越界。配置 allowedDependencies 后,还检查允许依赖列表。显式开放的模块允许内部类型访问。ArchUnit 可补充包依赖和自定义规则。
结构测试检查代码关系。跨模块 SQL 与运行时资源争用还需独立验证。
8. 模块的测试与观测
模块测试覆盖公开 API、领域规则和依赖契约。Spring Modulith 可缩小集成测试的模块范围。
日志和指标加入模块维度。事件积压、调用耗时和事务失败应能定位到模块。所有模块共享进程,CPU 与堆内存仍会相互影响。
9. 适用场景
边界仍在变化、团队规模有限时,模块化单体较易维护。它保留本地调用与共库事务的便利。接口、写入权和结构验证让边界可以持续检查。
某个模块有稳定的独立发布或隔离需求后,可评估拆分。目录拆开只是组织代码的一步。
10. 常见问题
10.1 Maven 模块与业务模块
Maven 模块提供构建边界。业务模块还需要明确契约、依赖和数据所有权。两者可以配合使用。
10.2 模块维护责任
一个模块可由多个团队协作。接口和表结构仍需明确决策人与评审人。团队调整时同步更新责任。
11 · 面试题卡片
11.1 模块化单体与微服务怎样选择
出现公司:字节跳动、美团
题型:综合题
考察重点
- 逻辑模块与部署单元的区别。
- 契约、依赖、数据所有权和结构检查。
- 独立部署收益与分布式成本。
参考回答
模块化单体适合边界仍在变化且运维资源有限的系统。
模块共享进程,本地调用与事务较简单。微服务适合独立发布、扩容或隔离收益明确的能力。
两者都需要业务接口和数据所有权。单体可用单向依赖及 CI 结构测试维护边界。服务拆分则增加网络失败、契约兼容和运行成本。选择依据是可验证的收益与维护能力。
11.2 模块事件在事务提交后处理,进程崩溃时如何恢复?
出现公司:暂无已核验的公开来源(本站自测)
考察重点:监听时机、持久登记与重复处理。
相关内容:事务事件与持久登记。
参考回答
持久事件登记为重启后的恢复提供依据。
业务变化与发布记录在同一事务中提交。监听器完成后标记结果,未完成记录用于重发。普通内存事件缺少这份记录。监听器仍需幂等,以覆盖成功后确认丢失。