Files
baiyue-firstleg/lastleg-service/docs/lastleg-internal-modularization-baseline.md
T

219 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lastleg 内部模块化依赖基线与首切片方案
## 1. 范围与生成方式
本基线只分析 `lastleg-service` 同一部署单元,不拆微服务,不修改业务流程、数据库、OpenAPI、权限、接口或跨服务契约。统计时工作区同时存在其他专项的在途修改;本专项不修改 `client/`、`config/*RestClient*` 或各业务 HTTP Gateway。模块内 POM 只增加由根 POM 统一版本管理的 ArchUnit 测试依赖。
可重复命令:
```powershell
node lastleg-service/scripts/analyze-lastleg-dependencies.mjs --source-set main --format markdown
node lastleg-service/scripts/analyze-lastleg-dependencies.mjs --source-set test --format markdown
node lastleg-service/scripts/analyze-lastleg-dependencies.mjs --source-set all --format markdown
node lastleg-service/scripts/analyze-lastleg-dependencies.mjs --source-set main --format dot --graph package
node lastleg-service/scripts/analyze-lastleg-dependencies.mjs --source-set test --format dot --graph package
node lastleg-service/scripts/analyze-lastleg-dependencies.mjs --source-set all --format dot --graph package
node lastleg-service/scripts/analyze-lastleg-dependencies.mjs --source-set main --format dot --graph slice
```
分析器解析显式内部 `import`、静态 `import` 及同包类型引用,输出类依赖、物理包图、逻辑切片图、Tarjan SCC、Entity/DTO/Model 泄漏与 Mapper 泄漏。`test` 单独输出测试包节点及测试类之间的边;当前测试主要引用生产类型,因此 test-only 图有 68 个包节点但没有测试类之间的内部边。`all` 把 `src/main/java` 和 `src/test/java` 合并解析,是观察测试对生产类型耦合的主图。Spring 扫描字符串、反射、MyBatis SQL 表引用和跨服务运行时调用不属于 Java 类型图。
## 2. 当前规模与 SCC
| 源集 | Java 文件 | 内部类依赖 | 物理包 | 跨切片类依赖 | 模型泄漏 | Mapper 泄漏 |
|---|---:|---:|---:|---:|---:|---:|
| main | 353 | 679 | 160 | 138 | 60 | 38 |
| test only | 103 | 0 | 68 | 0 | 0 | 0 |
| main + test | 456 | 1,206 | 164 | 257 | 108 | 76 |
主源码物理包图没有多节点 SCC。主源码逻辑切片图存在一个四节点 SCC:
```text
dropship
inbound-pda
inventory
tailleg-handoff
```
合并测试源码后,出现一个由测试直接绑定横向实现包形成的物理包 SCC:
```text
com.baiyue.lastleg.looseboard.service
com.baiyue.lastleg.relabel.service
com.baiyue.lastleg.service
com.baiyue.lastleg.transfer.service
```
这不是生产运行时包环。合并图的逻辑 SCC 扩大为 `dispatch-delivery`、`dropship`、`inbound-pda`、`inventory`、`procurement-trucking-looseboard`、`relabel-transfer-discard`、`tailleg-handoff` 七个切片,原因是跨域验收测试引用多个业务实现包。
## 3. 逻辑切片映射
| 逻辑切片 | 当前识别范围 | main 类数 | 跨切片出/入类边 | 当前结论 |
|---|---|---:|---:|---|
| 尾程交接 `tailleg-handoff` | 根 `controller/dto/entity/mapper/model/repository/service` 与 `shipping/` | 33 | 12/48 | 核心尾程单、事件 Inbox/Outbox 和船节点门禁被入仓、库存、换标等直接引用,位于主 SCC |
| 入仓/PDA `inbound-pda` | `inboundplan/`、`palletops/`、根 `pda/` | 100 | 53/14 | 最大切片;与尾程核心、库存和一件代发双向耦合,含大量库存 Entity/Mapper 泄漏 |
| 库存 `inventory` | `inventory/` | 26 | 3/37 | 自身出向较少,但作为多个作业域共享的 Entity/Mapper 实现被直接消费,位于主 SCC |
| 排车/派车 `dispatch-delivery` | `dispatch/`、`delivery/`、未来 `outbound/` | 46 | 20/0 | 主源码无入向 Java 类型边,但涉及明确排除的出库/派送范围,不作为首切片 |
| 换标/转运/废弃 `relabel-transfer-discard` | `relabel/`、`transfer/`、`discard/`,其 PDA 子包保持本域归属 | 61 | 31/0 | 直接使用尾程、库存和一件代发持久化模型;业务事务边界复杂 |
| 一件代发 `dropship` | `dropship/` | 12 | 4/11 | 与尾程消费及废弃库存双向耦合,位于主 SCC |
| 采购/卡车/散板 `procurement-trucking-looseboard` | `procurement/`、`trucking/`、`looseboard/` | 35 | 11/0 | 依赖尾程核心,采购另读取报价目录;仍由海外仓业务业务专项维护 |
| 海外主数据 `overseas-master-data` | `masterdata/quote`、`exchange`、`vehicle` | 28 | 3/1 | 不在任何 SCC;仅出向共享权限常量,入向来自采购对报价目录的使用 |
| 海外安全投影 `security-projection` | `security/controller`、`security/dto`、`security/service` | 6 | 1/0 | 独立于业务切片与主 SCC;唯一出向为内部 Controller 复用根 `InternalServiceTokenGuard`,无业务切片入向依赖 |
另有 6 个 `shared-technical` 类:应用启动类、Event Bridge Client、RestClient 配置、内部令牌保护和海外权限常量。该分类不授权移动这些共享类。
## 4. 主源码逻辑切片边
| 来源 | 目标 | 类边数 |
|---|---|---:|
| dispatch-delivery | dropship | 5 |
| dispatch-delivery | inbound-pda | 6 |
| dispatch-delivery | inventory | 4 |
| dispatch-delivery | shared-technical | 2 |
| dispatch-delivery | tailleg-handoff | 8 |
| dropship | inbound-pda | 1 |
| dropship | shared-technical | 1 |
| dropship | tailleg-handoff | 2 |
| inbound-pda | dropship | 1 |
| inbound-pda | inventory | 25 |
| inbound-pda | shared-technical | 8 |
| inbound-pda | tailleg-handoff | 19 |
| inventory | inbound-pda | 1 |
| inventory | shared-technical | 2 |
| overseas-master-data | shared-technical | 3 |
| procurement-trucking-looseboard | overseas-master-data | 1 |
| procurement-trucking-looseboard | shared-technical | 5 |
| procurement-trucking-looseboard | tailleg-handoff | 5 |
| relabel-transfer-discard | dropship | 8 |
| relabel-transfer-discard | inventory | 6 |
| relabel-transfer-discard | shared-technical | 3 |
| relabel-transfer-discard | tailleg-handoff | 14 |
| security-projection | shared-technical | 1 |
| tailleg-handoff | dispatch-delivery | 8 |
| tailleg-handoff | dropship | 2 |
| tailleg-handoff | inbound-pda | 6 |
| tailleg-handoff | inventory | 2 |
| tailleg-handoff | shared-technical | 2 |
当前 28 条切片边冻结在 `src/test/resources/architecture/lastleg-slice-dependencies.json`。合同允许删除历史边,不允许增加新边;海外主数据继续锁定为只允许出向 `shared-technical`,只允许采购/卡车/散板切片入向;安全投影只允许出向 `shared-technical` 且禁止任何业务切片入向依赖。
## 5. Entity/DTO/Mapper 泄漏基线
主源码共有 60 条跨切片 Entity/DTO/Model 泄漏、38 条跨切片 Mapper 泄漏。高频边如下:
| 来源 → 目标 | 模型 | Mapper | 代表性证据 |
|---|---:|---:|---|
| inbound-pda → inventory | 15 | 9 | 托盘、移库、盘点 Service/Mapper 直接使用 `InventoryBalance`、`InventoryPosition`、`InventoryLedger` 及其 Mapper |
| inbound-pda → tailleg-handoff | 9 | 9 | 入仓实收、卸柜完成直接使用根 `LastlegOrder*`、事件 Outbox Entity/Mapper |
| relabel-transfer-discard → dropship | 7 | 1 | 废弃直接读取一件代发箱/SKU 库存 Entity,调整服务写箱流水 Mapper |
| relabel-transfer-discard → tailleg-handoff | 6 | 6 | 换标、转运、废弃直接使用根尾程订单、明细、状态历史 Entity/Mapper |
| dispatch-delivery → tailleg-handoff | 4 | 4 | 拣货归属和派送转移直接使用根尾程订单与状态 Mapper;该范围不进入本专项首切片 |
| dispatch-delivery → inbound-pda | 4 | 2 | 派送转移直接使用入仓计划行、托盘分配 Entity/Mapper;该范围不进入本专项首切片 |
| relabel-transfer-discard → inventory | 4 | 1 | 转运直接使用库存余额/库位模型及库存命令 Mapper |
当前泄漏体现的是业务核心对其他切片持久化实现和表模型的直接依赖。后续治理应以应用 Port 和切片自有投影替换,不共享领域 Entity;本轮不做替换。
## 6. 首切片选择:车辆档案
选择 `masterdata.vehicle` 作为首个低耦合切片候选,依据当前主源码图:
1. 只有 5 个生产类,已经集中在单一子域;现有测试 3 个。
2. 零跨切片入向类依赖;唯一出向类依赖为 `PlatformVehicleController -> OverseasPermissions`。
3. 海外主数据不在 main 或 main+test 的逻辑 SCC 内,车辆子域也没有 Entity/DTO/Mapper 跨切片泄漏。
4. 不包含跨服务 Client、RestClient 或 HTTP Gateway,不受同步依赖超时专项影响。
5. 车辆档案明确不保存派送履约历史;首切片不触碰 `dispatch/`、`delivery/`、出库或派送代码。
6. 当前业务面固定为平台车辆档案列表、详情、创建、编辑、状态和 CSV 导出,不需要补齐海外仓业务业务缺口。
报价子域虽然也不在 SCC,但被采购服务通过 `PartnerWarehouseDirectory` 入向依赖,并包含 `DomesticPartnerWarehouseClient`,因此避让。月度汇率有 8 个生产类,也比车辆档案更大。
## 7. 已实施目标目录与精确文件
首切片已按下列结构实施:
```text
masterdata/vehicle/
adapter/in/web/
PlatformVehicleController.java
application/
VehicleApplicationService.java
model/VehicleModels.java
port/out/VehicleRepository.java
adapter/out/persistence/
VehiclePersistenceAdapter.java
VehicleMapper.java
VehicleRecord.java
```
精确动作草案:
| 原文件 | 已实施动作 |
|---|---|
| `masterdata/vehicle/controller/PlatformVehicleController.java` | 移至 `adapter/in/web/`;URI、权限、HTTP/CSV 响应不变 |
| `masterdata/vehicle/dto/VehicleDtos.java` | 移至 `application/model/VehicleModels.java`;JSON 字段、校验和响应语义不变 |
| `masterdata/vehicle/service/VehicleService.java` | 移至 `application/VehicleApplicationService.java`;业务校验、乐观锁和状态语义不变,只依赖 Out Port |
| 新增 `application/port/out/VehicleRepository.java` | 定义分页、详情、锁定、创建、乐观锁更新、状态更新和导出查询所需的切片内投影;不暴露 MyBatis 类型 |
| `masterdata/vehicle/entity/Vehicle.java` | 移至 `adapter/out/persistence/VehicleRecord.java`;仅作为 `ll_vehicle` 持久化记录 |
| `masterdata/vehicle/mapper/VehicleMapper.java` | 移至 `adapter/out/persistence/VehicleMapper.java`;保持现有 SQL 与 MyBatis 语义 |
| 新增 `adapter/out/persistence/VehiclePersistenceAdapter.java` | 实现 `VehicleRepository`,隔离 MyBatis `Page`、Wrapper、Mapper 和 Record |
允许依赖方向:
```text
adapter.in.web -> application -> application.model + application.port.out
adapter.out.persistence -> application.port.out + application.model
```
Web Adapter 可继续依赖 Spring Web、common-security 和 `OverseasPermissions`。`application` 禁止依赖 Spring MVC、MyBatis/MyBatis-Plus、全局 Mapper/Entity、RestClient、其他业务切片或出库/派送类型。
## 8. 测试与回滚边界
本次迁移只修改车辆子域 5 个原生产文件、新增 2 个车辆子域文件,并定向更新:
- `src/test/java/com/baiyue/lastleg/masterdata/vehicle/application/VehicleApplicationServiceTest.java`:改为 Mock `VehicleRepository`,继续锁定公里码、乐观锁和状态规则。
- `src/test/java/com/baiyue/lastleg/masterdata/vehicle/adapter/in/web/PlatformVehicleControllerContractTest.java`:继续锁定 URI 与六项权限。
- `src/test/java/com/baiyue/lastleg/masterdata/vehicle/VehicleSqlContractTest.java`:继续锁定 `ll_vehicle` SQL、唯一索引和字段注释。
- `scripts/analyze-lastleg-dependencies.test.mjs`:已锁定目标 7 类与 application 禁止依赖规则。
回滚只涉及上述车辆 Java 包移动、两个新增抽象及对应测试引用。不得修改或回滚共享 POM、`AGENTS.md`、RestClient 配置、数据库 SQL、OpenAPI、权限 SQL、接口路径、字段、状态、CSV 格式、Nacos、部署或其他业务域。若 Mapper 扫描或事务代理合同不满足,整体恢复车辆包内变更,不通过扩大全局扫描、兼容 Bean 或跨切片共享 Entity 绕过。
## 9. 当前只读合同
- `scripts/analyze-lastleg-dependencies.mjs`:生成 JSON、Markdown、逻辑切片 DOT、main/test/all 物理包 DOT 和 Tarjan SCC。
- `src/test/resources/architecture/lastleg-slice-dependencies.json`:冻结 26 条 main 切片边;历史边可减少,不可增加,并锁定安全投影 6 个类的位置、单一出向与零入向。
- `scripts/analyze-lastleg-dependencies.test.mjs`:校验基线、main/test/all 包图输出、车辆切片边界及安全投影隔离。
- `src/test/java/com/baiyue/lastleg/security/SecurityProjectionArchitectureTest.java`:用 ArchUnit 锁定投影内部只依赖本子域和根安全技术类、业务包不得反向依赖投影、DTO/Service 不依赖内部 Web Adapter。
最小验证:
```powershell
node --test lastleg-service/scripts/analyze-lastleg-dependencies.test.mjs
node lastleg-service/scripts/analyze-lastleg-dependencies.mjs --source-set main --format markdown
node lastleg-service/scripts/analyze-lastleg-dependencies.mjs --source-set test --format markdown
node lastleg-service/scripts/analyze-lastleg-dependencies.mjs --source-set all --format markdown
```
## 10. 首切片实施结果
- 旧 5 类及旧测试包已删除,没有转发兼容层;目标 7 类已由 Node 合同锁定。
- `VehicleApplicationService` 只依赖 `VehicleModels` 与 `VehicleRepository`,不依赖 MyBatis/MyBatis-Plus、Spring MVC、RestClient、全局 Mapper/Entity 或其他 Lastleg 切片。
- `VehiclePersistenceAdapter` 独占 MyBatis `Page`、Wrapper、Mapper 和 `VehicleRecord`,只向 Port 返回 Java 中立投影。
- 车辆切片保持零入向依赖,唯一出向仍是 Web Adapter 使用 `OverseasPermissions`;全局 25 条切片边、60 条模型泄漏、38 条 Mapper 泄漏及主 SCC 均未增加。
- 接口路径、六项权限、JSON 字段、校验、乐观锁、状态、CSV、SQL、表和索引保持不变。
- 验证结果:Node 合同 4/4;`mvn -pl lastleg-service -am test-compile` 成功;车辆定向测试 5/5;`mvn -pl lastleg-service -am test` 中 lastleg-service 370 个测试,0 失败、0 错误、2 个环境验收跳过,构建成功。
首切片完成后停止,不进入第二切片。
## 11. 海外安全投影合入后的边界
- 用户确认安全投影保留在 `lastleg-service` 部署单元内,不拆新微服务;逻辑上独立为 `security-projection` 切片。
- Controller 仅映射 `/internal/lastleg/security/v1/events` 与 `/validate`,使用 `@Hidden` 并在进入投影或校验服务前调用根 `InternalServiceTokenGuard`。
- MySQL 表固定使用 Lastleg 前缀 `ll_security_event_inbox`、`ll_security_projection`;没有 `overseas_security_*` 表或独立数据库归属。
- `loading`、`shipping`、`inboundplan` 及其他业务切片不得直接依赖 `security/controller|dto|service`;当前 Java 类型图零入向,Node 与 ArchUnit 双重锁定。
- 安全投影不进入主逻辑 SCC,唯一出向为内部 Controller 复用共享令牌守卫,不注册为 loading/shipping/inbound 的模型、Mapper 或应用服务。
## 12. 海外仓出库与派送出库派送稳定边界
- `dispatch-delivery -> dropship` 是一件代发出库的稳定业务依赖:出库应用服务按客户范围读取一件代发托盘、箱唛和 SKU 库存,并在 PDA 出库事务中完成扣减。
- `tailleg-handoff -> dispatch-delivery` 是异常退回与客户回访的稳定业务依赖:异常单和回访应用服务按派送业务号读取派送单及操作记录,不引入反向兼容入口或跨服务共享模型。
- 两条边均属于同一 `lastleg-service` 部署单元内的海外仓出库与派送正式链路;本次只冻结已存在的生产依赖,不扩大海外主数据或安全投影的允许方向。