4.1 POM 编写规范与多模块治理 本节摘要:多模块工程用 modules 聚合管"一起构建"、用 parent 继承管"配置复用",Reactor 按依赖图拓扑排序决定构建顺序。规范层面要求版本进 properties、仲裁进父 POM 管理段、坐标命名有章法。本节完成 risk-engine 的多模块化改造。 一、膨胀到必须拆的那一天 risk-engine 单模块时代终结于第三十万个 commit:规则解析、数据接入、对外 API、公共工具全挤在一个 POM 里,任何一行改动的构建与测试都要全量跑一遍,发布也要整包出。拆分方案按职责切四刀: 拆分的原则是依赖方向单向、公共层无外部依赖、组装层只有一到两个。判断一个拆分好不好,看两件事:改一个模块能不能只构建它自己;
本节摘要:多模块工程用 modules 聚合管"一起构建"、用 parent 继承管"配置复用",Reactor 按依赖图拓扑排序决定构建顺序。规范层面要求版本进 properties、仲裁进父 POM 管理段、坐标命名有章法。本节完成 risk-engine 的多模块化改造。
risk-engine 单模块时代终结于第三十万个 commit:规则解析、数据接入、对外 API、公共工具全挤在一个 POM 里,任何一行改动的构建与测试都要全量跑一遍,发布也要整包出。拆分方案按职责切四刀:
risk-parent 父工程 只放 POM 与公共配置 risk-common 公共工具与常量 无外部依赖 risk-rule 规则解析引擎 依赖 common risk-dao 数据接入层 依赖 common risk-web 对外 API 组装 依赖 rule 与 dao
拆分的原则是依赖方向单向、公共层无外部依赖、组装层只有一到两个。判断一个拆分好不好,看两件事:改一个模块能不能只构建它自己;新模块加进来要不要动公共层。
初学者最常混淆的概念对。聚合(modules)管一起构建:父 POM 列出子模块,一条命令全量构建;继承(parent)管配置复用:子模块声明父 POM,继承仲裁表、插件配置与属性。两件事可以拆开用,但常规工程两者都上:
<!-- risk-parent 的 POM:聚合与中央配置 --> <groupId>com.shop.risk</groupId> <artifactId>risk-parent</artifactId> <version>2.0.0</version> <packaging>pom</packaging> <!-- packaging 为 pom:父工程没有代码 只有结构 --> <modules> <!-- 聚合:一条 mvn 命令按序构建全部子模块 --> <module>risk-common</module> <module>risk-rule</module> <module>risk-dao</module> <module>risk-web</module> </modules> <properties> <!-- 版本统一进属性:升级改一处 --> <jackson.version>2.13.4</jackson.version> <spring.boot.version>2.7.14</spring.boot.version> </properties> <dependencyManagement> <dependencies> <!-- 第 3.3 节的仲裁表原样迁入 子模块全部服从 --> <dependency> <groupId>com.fasterxml.jackson</groupId> <artifactId>jackson-bom</artifactId> <version>${jackson.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 内部模块也进管理段:版本由中央统一 --> <dependency> <groupId>com.shop.risk</groupId> <artifactId>risk-common</artifactId> <version>${project.version}</version> </dependency> </dependencies> </dependencyManagement>
<!-- risk-rule 的 POM:继承与瘦身后的声明 --> <parent> <groupId>com.shop.risk</groupId> <artifactId>risk-parent</artifactId> <version>2.0.0</version> <!-- relativePath 指向父 POM 默认 ../pom.xml 可显式声明 --> <relativePath>../pom.xml</relativePath> </parent> <artifactId>risk-rule</artifactId> <!-- groupId 与 version 从父继承 无需重复 --> <dependencies> <dependency> <groupId>com.shop.risk</groupId> <artifactId>risk-common</artifactId> <!-- 版本来自父管理段 此处不写 --> </dependency> </dependencies>
规范化后子模块的 POM 普遍只有几十行:坐标、依赖列表、必要的插件配置。版本号在子模块里出现,就是建制的破口——要么进父 properties,要么进管理段,两处之外的版本字面量都该在 review 里被打回。
执行全量构建时,Maven 先读入所有模块的 POM,构建模块间依赖图,按拓扑序排出 Reactor 顺序——被依赖者先构建,依赖者后构建。日志开头那张 Reactor 摘要表就是顺序表:
[INFO] Reactor Build Order: risk-common 公共层无依赖 排第一 risk-dao 依赖 common 排第二 risk-rule 依赖 common 排第三 与 dao 无依赖关系 位置可互换 risk-web 依赖 rule 与 dao 排最后
Reactor 的价值在部分构建。只改了规则引擎,没必要全量:
# 只构建 risk-rule 及其上游依赖 -am = also make mvn clean install -pl risk-rule -am # 输出显示只构建 common 与 rule 两块 # 反向:构建依赖了 risk-rule 的下游 -amd = also make dependents mvn clean install -pl risk-rule -amd # 构建 rule 与 web 用于评估改动的影响面
两把剪刀的战术用途不同:开发提速用 -am,变更影响评估用 -amd。CI 上常用组合拳:日常流水线 -pl 变更模块 -am,夜间流水线全量兜底。
⚠️ 常见坑:模块间出现循环依赖时 Reactor 直接报错且提示含糊("循环引用"三个字加一串模块名)。处置是重新审视拆分边界——循环依赖几乎总是"公共层装了不该装的东西"的症状,把肇事代码下沉到 common 或抽出新模块,而不是想办法绕过校验。
💡 关键直觉:父 POM 是团队的"宪法",子 POM 是"地方法规"。判断一条配置该放哪的规则只有一条:需要全模块一致的进父(版本仲裁、编译参数、发布配置),模块个性化的留本地(专属插件、本地资源)。宪法越稳定,地方法规越薄,工程越健康。
结构已拆好,仲裁已上收。但构件们的存放地还乱着——下一节治理仓库:本地缓存的卫生学、快照与发布的分仓策略。