20 KiB
Lastleg 内部模块化依赖基线与首切片方案
1. 范围与生成方式
当前维护入口(2026-10-02)
现行门禁为 scripts/analyze-lastleg-dependencies.mjs、对应 .test.mjs 和 src/test/resources/architecture/lastleg-slice-dependencies.json。下文第2至8节的规模、原安全投影和首切片说明保留为早期对比,不覆盖本节及当前正式统一认证契约。此次校正不修改既有跨业务流程,也不以新增模块级通配许可接受未知依赖。
- 统一认证使用common-security及Auth权威会话。旧6个LastlegSecurity投影类型不再要求存在,改为禁止恢复该切片及引用其退役类型;不存在的退役类被重新import也会失败。
- 25条已核对的既有类引用逐条写入
reviewedClassDependencies,必须有完整源类、目标类及原因,拒绝通配符。尾程订单直接换标审核委托换标服务;退货实收转换标在同服务事务中建立申请及明细;仓储转运实测入仓同步计划/分配/收货事实并复用资源网关;PDA入口共用冻结仓身份;费用换算只由既有换算器读取月度汇率。此记录不授权同一模块其他类使用这些实现。 - 未审阅的新增类引用仍按原切片门禁拦截;车辆应用层的向内依赖规则对所有引用执行,不能通过逐类记录绕过。fleet继续只消费车辆应用端口/服务,不读取车辆持久化适配器。
- 主源码禁止包循环。
DeliveryDocumentSnapshot是DTO快照解析和摘要装配,已从delivery/model移至delivery/service,消除原DTO与model互相引用;枚举及公开JSON结构不变。不为循环设长期例外。 - 离线反向测试覆盖未授权新消费者、退役类型恢复/引用、通配记录、绕过车辆应用层和新增包环。通过门禁只证明这些结构规则,不证明消除了所有存量耦合或完成部署/业务验收。
车次接入边界
车次关联客户排车单新增 dispatch-delivery→fleet:fleet声明 FleetDispatchGateway,dispatch内适配器锁定自身排车/出库/派送并同步车和司机快照;子排车修改与发车使用fleet公开的关联门禁。fleet不导入dispatch实体、Mapper或业务服务,因此不新增双向切片依赖。车次与客户单据的归属不变,关联和同步处于同一Lastleg事务;客户单据已经开工时不允许通过改派覆盖执行事实。
当前分析器把 wallet.WalletCommandOutboxService 归入 shared-technical,它依赖尾程Outbox实体/Mapper;粗粒度图因而存在 fleet→shared-technical→tailleg-handoff→dispatch-delivery→fleet 聚合环。不能把这一聚合环等同于fleet包或Spring组件循环,也不能宣称切片图无环。门禁检查实际包无环、fleet不反向导入dispatch/delivery、dispatch只使用公开端口/门禁;粗粒度切片图保留为耦合报告,不因门禁通过隐去这条聚合环。
2026-10-02 新增 fleet 业务切片,归入当前 Lastleg 部署单元;司机档案与文件适配在 fleet/driver,计划与资源占用在 fleet/trip,纯资格、分段容量和成本分摊在 fleet/domain。fleet依赖技术HTTP配置,以及车辆应用层的 VehicleRepository / VehicleAssignmentGuard 端口和已有状态服务;新增 fleet→overseas-master-data 是同一物理车辆主档和车次占用的明确业务协作,不允许直接依赖车辆持久化适配器。车辆应用层继续只依赖自身端口,注入的占用检查实现归fleet。分析器显式分类并验证这些边界,旧统计及退役安全投影断言不代表当前架构事实。
本基线只分析 lastleg-service 同一部署单元,不拆微服务,不修改业务流程、数据库、OpenAPI、权限、接口或跨服务契约。统计时工作区同时存在其他专项的在途修改;本专项不修改 client/、config/*RestClient* 或各业务 HTTP Gateway。模块内 POM 只增加由根 POM 统一版本管理的 ArchUnit 测试依赖。
可重复命令:
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:
dropship
inbound-pda
inventory
tailleg-handoff
合并测试源码后,出现一个由测试直接绑定横向实现包形成的物理包 SCC:
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 作为首个低耦合切片候选,依据当前主源码图:
- 只有 5 个生产类,已经集中在单一子域;现有测试 3 个。
- 零跨切片入向类依赖;唯一出向类依赖为
PlatformVehicleController -> OverseasPermissions。 - 海外主数据不在 main 或 main+test 的逻辑 SCC 内,车辆子域也没有 Entity/DTO/Mapper 跨切片泄漏。
- 不包含跨服务 Client、RestClient 或 HTTP Gateway,不受同步依赖超时专项影响。
- 车辆档案明确不保存派送履约历史;首切片不触碰
dispatch/、delivery/、出库或派送代码。 - 当前业务面固定为平台车辆档案列表、详情、创建、编辑、状态和 CSV 导出,不需要补齐海外仓业务业务缺口。
报价子域虽然也不在 SCC,但被采购服务通过 PartnerWarehouseDirectory 入向依赖,并包含 DomesticPartnerWarehouseClient,因此避让。月度汇率有 8 个生产类,也比车辆档案更大。
7. 已实施目标目录与精确文件
首切片已按下列结构实施:
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 |
允许依赖方向:
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:改为 MockVehicleRepository,继续锁定公里码、乐观锁和状态规则。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_vehicleSQL、唯一索引和字段注释。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。
最小验证:
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独占 MyBatisPage、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部署单元内的海外仓出库与派送正式链路;本次只冻结已存在的生产依赖,不扩大海外主数据或安全投影的允许方向。
2026-10-03 外接来货使用FleetBookingIncomingGateway,仅transfer.fleet.FleetIncomingAdapter实现该端口并复用FleetDispatchGateway的目的键算法;这两个精确类引用进入reviewedClassDependencies,不放开整个转运切片读取fleet实现。fleet不导入转运DTO/Service/Mapper,转运仍维护实收与库存;车次只保存实收证据和确认的承运载荷。包无环及禁止其他消费者扩大依赖的反向测试继续生效。