258 lines
18 KiB
Markdown
258 lines
18 KiB
Markdown
# Firstleg 内部模块化基线与切片实施记录
|
||
|
||
## 1. 范围与生成方式
|
||
|
||
本基线只分析 `firstleg` 同一部署单元,不拆微服务,不改变业务流程、数据库结构、OpenAPI 或跨服务契约。分析输入为:
|
||
|
||
- 首切片实施前 `src/main/java`:429 个 Java 文件;948 条可解析的模块内类依赖;66 个物理包。
|
||
- 首切片实施前 `src/test/java`:169 个 Java 文件。与主源码合并分析时共 598 个 Java 文件、1,635 条模块内类依赖、69 个物理包。
|
||
- 首切片实施后 `src/main/java`:430 个 Java 文件;949 条可解析的模块内类依赖;67 个物理包。增加项仅为只读 Out Port。
|
||
|
||
可重复命令:
|
||
|
||
```powershell
|
||
node firstleg/scripts/analyze-firstleg-dependencies.mjs --source-set main --format markdown
|
||
node firstleg/scripts/analyze-firstleg-dependencies.mjs --source-set all --format json
|
||
node firstleg/scripts/analyze-firstleg-dependencies.mjs --source-set main --format dot
|
||
```
|
||
|
||
分析器解析显式内部 `import`、静态 `import` 及同包类型引用,输出物理包图、逻辑切片图、SCC、跨切片类依赖、Entity/DTO 泄漏和 Mapper 泄漏。Spring 字符串扫描路径、反射、MyBatis SQL 中的表引用不属于 Java 类型图,因此在本报告单独标注。
|
||
|
||
## 2. 当前 SCC
|
||
|
||
### 2.1 主源码物理包 SCC
|
||
|
||
当前只有一个多节点物理包 SCC:
|
||
|
||
```text
|
||
com.baiyue.firstleg.mapper
|
||
<-> com.baiyue.firstleg.repository
|
||
```
|
||
|
||
直接证据是全局 Repository 大量依赖全局 Mapper,同时 `InboundAllocationQueryMapper` 反向引用 `repository.PriceBookQueryRows`。这说明全局数据访问层尚不能按传统横向层直接单向化。
|
||
|
||
合并测试源码后,测试对生产类型的依赖把 SCC 扩大为下列十包;这不是生产运行时环,但表示测试也直接绑定了横向实现包:
|
||
|
||
```text
|
||
controller -> loading.repository -> loading.service -> mapper
|
||
-> oceanbooking.controller -> oceanbooking.repository -> oceanbooking.service
|
||
-> pricing -> repository -> service -> controller
|
||
```
|
||
|
||
### 2.2 逻辑切片 SCC
|
||
|
||
逻辑切片图存在一个八节点 SCC:
|
||
|
||
```text
|
||
booking-customs-trucking
|
||
finance-integration
|
||
inbound
|
||
measurement-pricing
|
||
outbound
|
||
shared-technical
|
||
shipping-nodes
|
||
split-loading
|
||
```
|
||
|
||
`customer-statistics` 不在 SCC 内。当前 36 条切片间边均冻结在 `src/test/resources/architecture/firstleg-slice-dependencies.json`;合同允许删除现有边,不允许增加新边。
|
||
|
||
主要双向依赖包括:
|
||
|
||
| 双向关系 | 直接证据 |
|
||
|---|---|
|
||
| 入库 ↔ 测量计价 | 入库编排直接使用测量/费用 Entity、Mapper;计价与客户费用查询直接使用入库单、状态和 Outbox |
|
||
| 入库 ↔ 分柜装柜 | 统一入库读模型读取装柜/PDA Mapper 与 Entity;装柜结算和事件发布读取入库状态、托盘与 Outbox |
|
||
| 分柜装柜 ↔ 出仓 | 货柜聚合读出仓历史;海外卸柜回写反向更新货柜/装柜事实 |
|
||
| 订舱报关拖车 ↔ 出仓 | 出仓门禁聚合报关/船期贡献者;报关和船期贡献者反向依赖出仓 DTO |
|
||
| 订舱报关拖车 ↔ 分柜装柜 | 订舱货柜分配使用装柜实体;报关直接读取装柜计划与明细 Mapper |
|
||
| 订舱报关拖车 ↔ 测量计价 | 价格簿校验影响订舱;供应商/渠道校验又由全局计价服务消费 |
|
||
|
||
## 3. 八个业务切片现状映射
|
||
|
||
类数量和跨切片依赖数量以主源码图为准;“出/入”均为类级直接依赖条数。
|
||
|
||
| 业务切片 | 当前物理位置与识别范围 | 类数 | 出/入 | 主要边界问题 |
|
||
|---|---|---:|---:|---|
|
||
| 入库 | 全局 `controller/service/repository/mapper/entity/dto` 中 `Inbound*`、`Fulfillment*`、`Assignment*`、`UnifiedInbound*` | 67 | 26/60 | 被计价和装柜大量反向读取;`InboundWorkflowRepository` 直接持有测量/费用 Mapper |
|
||
| 测量计价 | 全局层中的 `Measurement*`、`Mojia*`、`Pricing*`、`PriceBook*`、`Fba*`、`Dimension*`、`FeeItem*`、`InboundFee*`,以及 `pricing/` | 113 | 72/25 | 最大切片;对入库有 35 条直接依赖,其中 20 条为跨切片数据模型泄漏、6 条为 Mapper 泄漏 |
|
||
| 分柜装柜 | `split/`、`loading/` 加全局 `Loading*`、`SmartLoading*`、货柜与 PDA 类 | 82 | 50/32 | 同时依赖入库、订舱、出仓和共享层;全局 Repository 直接读取 `loading/split` Mapper/Entity |
|
||
| 订舱报关拖车 | `oceanbooking/`、`customs/`,全局 `TruckAppointment*`、`WarehouseSupplier*`、出仓门禁贡献者 | 68 | 25/13 | `CustomsRepository` 直接读取入库与装柜 Mapper;订舱 Repository 直接读取全局货柜 Entity |
|
||
| 出仓 | `outbound/dispatch/` 与全局 `OutboundDispatch*`、运输历史、海外卸柜回写 | 17 | 8/10 | 与装柜、订舱门禁双向;海外回写直接使用全局货柜 Mapper/Entity |
|
||
| 船节点 | `portschedule/` 中 `CarrierScheduleQuery/Node/Adapter/Attempt/Exception*` | 22 | 8/1 | 查询服务直接读取订舱、船期、货柜 Entity;节点 Outbox 直接使用入库 Outbox Mapper/Entity |
|
||
| 客户统计 | `customerstatistics/adapter`、`customerstatistics/application` | 5 | 1/0 | 已完成首切片;唯一跨切片依赖为 Web Adapter 调用 `FirstlegScopeGuard`。SQL 只读订舱、货柜和装柜表 |
|
||
| 财务集成 | 全局 `InternalFinance*`、`FinanceFeeSource*`、`FinanceCostSnapshot*`、付款凭证/快照类 | 20 | 17/6 | 直接读取入库费、柜次费、装柜及共享 Outbox 的 Entity/Mapper;继续受财务总控冻结的业务口径约束 |
|
||
|
||
共享技术层当前有 36 类,包含应用启动/配置、Scope Guard、单据号、部分跨服务 Client 及无法安全归属的全局模型。跨服务适配器按 `client` 包独立标识,不能因逻辑切片归属而被迁入应用核心。
|
||
|
||
## 4. 泄漏与逆向依赖基线
|
||
|
||
主源码共有 88 条跨切片 Entity/DTO 泄漏、31 条跨切片 Mapper 泄漏。高频项如下:
|
||
|
||
| 来源 → 目标 | Entity/DTO | Mapper | 代表性证据 |
|
||
|---|---:|---:|---|
|
||
| 测量计价 → 入库 | 20 | 6 | `FbaPricingRepository -> InboundOutboxEntity/Mapper`;客户费用查询直接使用 `InboundOrderDetail` |
|
||
| 分柜装柜 → 入库 | 7 | 6 | `LoadingDeliveryService -> InboundStatusHistoryEntity/Mapper`;装柜 Outbox 复用入库 Outbox |
|
||
| 入库 → 测量计价 | 4 | 4 | `InboundWorkflowRepository -> InboundFee/Measurement/Review Mapper` |
|
||
| 入库 → 分柜装柜 | 4 | 3 | `UnifiedInboundReadRepository` 直接读取装柜证据、PDA 任务、预装计划 Mapper/Entity |
|
||
| 财务集成 → 入库 | 4 | 2 | 财务来源 Outbox 直接复用 `InboundOutboxEntity/Mapper` |
|
||
| 订舱报关拖车 → 分柜装柜 | 3 | 3 | `CustomsRepository -> LoadingPlan/LoadingPlanItem Mapper` |
|
||
| 分柜装柜 → 订舱报关拖车 | 3 | 0 | `ContainerService -> OceanBookingContainer*Entity` |
|
||
| 船节点 → 入库 | 1 | 1 | `CarrierScheduleNodeOutboxService -> InboundOutboxEntity/Mapper` |
|
||
|
||
逆向依赖的判定不是简单的 `controller -> service -> repository` 层次关系,而是“业务核心直接依赖另一切片的持久化实现或 Entity”。首轮治理应优先用查询/命令 Port 替代这些直接引用,不共享领域 Entity。
|
||
|
||
## 5. 首切片选择:客户统计
|
||
|
||
选择 `customer-statistics`,不是因为预设,而是因为以下实际证据:
|
||
|
||
1. 实施前仅 4 个生产类且已集中在单一顶层包;实施后为原 4 类加 1 个只读 Out Port,没有业务切片对其产生 Java 类型入向依赖。
|
||
2. 唯一跨切片 Java 依赖为 Web Controller 对共享 `FirstlegScopeGuard` 的调用,不涉及 Entity 或 Mapper 泄漏。
|
||
3. 不属于当前八节点 SCC,也没有跨服务 Feign/HTTP Client。
|
||
4. 现有调用面稳定:一个平台 GET 接口、一个 Admin API 消费者、两个后端测试。
|
||
5. 它仍包含一个明确的内部逆向依赖:应用 Service 直接依赖 MyBatis Mapper,适合用最小 Out Port 证明目标依赖方向。
|
||
|
||
限制:`CustomerTransitStatisticsMapper` 的 SQL 只读 `fl_ocean_booking`、`fl_container_booking`、`fl_loading_plan`、`fl_loading_plan_item`。首切片只隔离 Java 依赖,不宣称拥有这些表,也不改变 SQL、索引或统计口径。
|
||
|
||
## 6. 首切片目标目录与允许依赖
|
||
|
||
```text
|
||
customerstatistics/
|
||
adapter/in/web/
|
||
PlatformCustomerTransitStatisticsController.java
|
||
application/
|
||
CustomerTransitStatisticsQueryService.java
|
||
model/CustomerTransitStatisticsModels.java
|
||
port/out/CustomerTransitStatisticsReader.java
|
||
adapter/out/persistence/
|
||
CustomerTransitStatisticsMapper.java
|
||
```
|
||
|
||
允许方向:
|
||
|
||
```text
|
||
adapter.in.web -> application
|
||
adapter.out.persistence -> application.port.out
|
||
application -> application.model + common-core
|
||
```
|
||
|
||
额外允许项仅限 Web 适配器使用 `common-security`、Spring Web 和现有 `FirstlegScopeGuard`。禁止 `application` 依赖 MyBatis、Spring MVC、全局 `entity/mapper/repository/service` 或其他业务切片。
|
||
|
||
## 7. 精确迁移清单、调用方与测试
|
||
|
||
首切片已按以下清单迁移/新增:
|
||
|
||
| 当前文件 | 目标/动作 |
|
||
|---|---|
|
||
| `customerstatistics/controller/PlatformCustomerTransitStatisticsController.java` | 移到 `adapter/in/web/`,URI、权限和响应保持不变 |
|
||
| `customerstatistics/dto/CustomerTransitStatisticsDtos.java` | 移到 `application/model/`,保持 JSON 字段与数值语义不变 |
|
||
| `customerstatistics/service/CustomerTransitStatisticsService.java` | 移到 `application/` 并改名为 Query Service,只依赖 Out Port |
|
||
| `customerstatistics/mapper/CustomerTransitStatisticsMapper.java` | 移到 `adapter/out/persistence/`,实现/扩展只读 Out Port;SQL 原样保留 |
|
||
| 新增 `customerstatistics/application/port/out/CustomerTransitStatisticsReader.java` | 定义 `summarize(customerId)` 与只读投影,不暴露 MyBatis 类型 |
|
||
| `src/test/java/.../customerstatistics/CustomerTransitStatisticsServiceTest.java` | Mock Out Port,不再 Mock Mapper |
|
||
| `src/test/java/.../customerstatistics/CustomerTransitStatisticsContractTest.java` | 更新包引用,继续锁定 URI、权限、SQL、OpenAPI 与索引语义 |
|
||
| `BaiyueFirstlegApplication.java`、`BaiyueFirstlegApplicationContractTest.java` | 只有 Mapper 扫描路径因移动确需调整时修改;不改变其他扫描包 |
|
||
|
||
外部调用方只读核对,不在首切片修改:
|
||
|
||
- `firstleg/src/main/resources/openapi/customer-statistics-v1.yaml`
|
||
- `apps/admin-web/src/modules/container-loading/api/platformContainerLoading.ts`
|
||
- `apps/admin-web/src/modules/container-loading/api/platformContainerLoading.test.ts`
|
||
|
||
## 8. 回滚边界
|
||
|
||
- 不修改表、SQL 内容、接口路径、权限码、响应字段、统计定义、Nacos、部署或跨服务契约。
|
||
- 回滚只涉及上述客户统计 Java 包移动、Out Port 和对应测试;可整体恢复到现有四类结构。
|
||
- 若 Mapper 继承 Out Port 导致 MyBatis 代理行为或扫描合同失败,停止实施并恢复包内变更,不以增加全局扫描、共享 POM 依赖或兼容 Bean 绕过。
|
||
- 首切片完成前不开始第二切片。
|
||
|
||
## 9. 当前只读合同与后续协作
|
||
|
||
本轮已增加模块内、无第三方依赖的只读合同:
|
||
|
||
- `scripts/analyze-firstleg-dependencies.mjs`:生成 JSON/Markdown/DOT 和 Tarjan SCC。
|
||
- `src/test/resources/architecture/firstleg-slice-dependencies.json`:冻结当前切片边;历史边可减少,不可增加。
|
||
- `scripts/analyze-firstleg-dependencies.test.mjs`:验证基线无新增边,并锁定客户统计目标 5 类/目录、唯一出向边、零入向边、不进入 SCC 和 application 依赖禁令。
|
||
|
||
与其他架构专项的依赖:
|
||
|
||
- 专项一(拓扑与质量门禁):决定是否引入 ArchUnit、如何把本合同接入 Maven/CI、以及共享 POM 和全局质量门禁。当前未修改 POM;建议由专项一把本脚本作为 Firstleg 局部门禁接入,再以 ArchUnit 替换/增强源码级解析。
|
||
- 专项二(同步依赖与失败传播):在为其他切片定义应用 Port 前提供 Feign/HTTP 调用图和失败语义。客户统计当前没有跨服务 Client,因此首切片无需等待专项二;后续入库、财务集成、订舱和装柜切片必须协调。
|
||
|
||
## 10. 最小验证命令
|
||
|
||
```powershell
|
||
node --test firstleg/scripts/analyze-firstleg-dependencies.test.mjs
|
||
node firstleg/scripts/analyze-firstleg-dependencies.mjs --source-set main --format markdown
|
||
mvn -pl firstleg -am test-compile
|
||
mvn -pl firstleg -am -Dtest=CustomerTransitStatisticsServiceTest,CustomerTransitStatisticsContractTest -Dsurefire.failIfNoSpecifiedTests=false test
|
||
```
|
||
|
||
首切片已经发生 Java 包与依赖签名变化,三条命令均为直接门禁;另需执行 `mvn -pl firstleg -am test-compile` 覆盖全部测试源码调用点。
|
||
|
||
## 11. 首切片实施结果
|
||
|
||
- 四个旧包类已删除,没有转发兼容层;目标五层目录已由 Node 合同锁定。
|
||
- `CustomerTransitStatisticsReader.SummaryProjection` 只包含 `customerId`、`containerCount`、`totalTeu`、`unknownContainerCount`、`asOf`,不暴露 MyBatis、Entity 或其他切片类型。
|
||
- 依赖方向由 `application Service -> MyBatis Mapper` 改为 `application Service -> application Port <- persistence Mapper`。
|
||
- 主源码由 429 类/948 条内部依赖/66 包变为 430 类/949 条内部依赖/67 包;36 条切片间边、88 条模型泄漏、31 条 Mapper 泄漏及全局 `mapper <-> repository` SCC 均未增加。客户统计仍只有 `adapter.in.web -> FirstlegScopeGuard` 一条跨切片边,且没有入向边。
|
||
- 根 Mapper 扫描只把旧精确包替换为新精确包,没有扩大扫描范围或增加兼容 Bean。
|
||
- 验证结果:Node 合同 3/3;`test-compile` 成功;客户统计加根扫描定向测试 7/7;`mvn -pl firstleg -am test` 共 854 个测试,0 失败、0 错误、3 跳过,构建成功。
|
||
- 首次并发执行 `test-compile` 时,其他专项同时写入共享 `target/classes` 导致若干无关 class 文件瞬时缺失;源符号核对正常,独立重跑 `test-compile` 与完整测试均成功。
|
||
|
||
## 12. 第二切片:价格簿只读查询
|
||
|
||
### 12.1 选择证据
|
||
|
||
第二切片选择 `pricebook-query`,而没有选择报关、拖车、出仓或船节点:
|
||
|
||
- 原实现只有 `PriceBookQueryRows`、`PriceBookQueryRepository`、`PriceBookQueryMapper` 三个核心文件,全部为只读查询,不承载 Controller、权限或状态机。
|
||
- `PriceBookQueryMapper -> repository.PriceBookQueryRows` 是主源码唯一 `mapper -> repository` 反向依赖,也是物理包 SCC `mapper <-> repository` 的唯一成因。
|
||
- 报关和拖车均涉及 MultipartFile、Feign、装柜或出仓门禁;出仓和船节点属于当前业务热点,风险高于只读价格簿查询。
|
||
- 本切片不触碰 `LoadingPickService`、客户尺寸初始化、IAM 解环、POM、CI、AGENTS、SQL 或部署配置。
|
||
|
||
### 12.2 目标结构与依赖方向
|
||
|
||
```text
|
||
pricebookquery/
|
||
application/
|
||
PriceBookQueryService.java
|
||
model/PriceBookQueryRows.java
|
||
port/in/PriceBookQueryUseCase.java
|
||
port/out/PriceBookQueryReader.java
|
||
adapter/out/persistence/
|
||
PriceBookQueryMapper.java
|
||
```
|
||
|
||
依赖由“多个业务 Service -> 全局 Repository -> 全局 Mapper -> Repository Rows”改为:
|
||
|
||
```text
|
||
业务消费者 -> PriceBookQueryUseCase
|
||
PriceBookQueryService -> PriceBookQueryReader
|
||
PriceBookQueryMapper -> PriceBookQueryReader + PriceBookQueryRows
|
||
```
|
||
|
||
消费者只能访问 In Port 和只读模型;application 不依赖 Spring MVC、MyBatis、MyBatis-Plus 或其他 Firstleg 切片。Mapper 的七段 `@Select` SQL 文本和方法参数保持不变。
|
||
|
||
### 12.3 依赖图变化
|
||
|
||
- 主源码由 430 类/949 条内部依赖/67 包变为 432 类/953 条内部依赖/72 包;新增项为 In Port、Out Port 和 Application Service,原 Repository 被删除。
|
||
- 物理包多节点 SCC 从 1 个降为 0 个。
|
||
- `pricebook-query` 是零出向边的叶子,不进入逻辑 SCC;显式入向仅为订舱、测量计价、分柜装柜三个消费者切片。
|
||
- 原 `split-loading -> measurement-pricing` 两条价格簿引用被替换为显式 `split-loading -> pricebook-query`;Node 合同没有简单放宽旧边。
|
||
- 既有 88 条跨切片 Entity/DTO 泄漏和 31 条 Mapper 泄漏均未增加。
|
||
|
||
### 12.4 棘轮与回滚边界
|
||
|
||
- Node 合同锁定五个目标类位置、application 依赖禁令、零出向/三类入向和消费者只能访问 In Port/模型,并断言物理包 SCC 为空。
|
||
- `PriceBookQueryArchitectureTest` 用 ArchUnit 执行同等边界,持久化 Mapper 必须实现 Out Port。
|
||
- 回滚仅恢复原三文件及消费者类型引用;不涉及 URI、权限、JSON、SQL、表结构、业务状态或环境配置。
|
||
- 只读模型仍保留原 Lombok 可变 Bean 形态以维持 MyBatis 映射行为;本轮不顺带改造成不可变领域对象。
|
||
- 验证结果:七段 `@Select` SQL 文本逐块完全一致;Node 合同 5/5;`test-compile` 成功;价格簿、ArchUnit、根扫描及三个消费者切片定向测试 78/78;Firstleg 聚合全量 878 个测试,0 失败、0 错误、3 跳过,构建成功。
|
||
|
||
|
||
## 预约工作集成时的精确边界核对
|
||
|
||
`PlatformContainerShippingNodeController` 归船节点切片,`sailingcalendar` 归订舱船期切片;不再误归共享技术层。既有海外事件适配、实装费用重算和渠道目录读取在 `reviewedClassDependencies` 按具体调用者、目标类型和理由登记,不放开整个切片。
|
||
|
||
现存全局 service 包同时承载暂扣、装柜费用和订舱文档编排,形成 loading.bridge.service、loading.service、oceanbooking.service、service 四包 SCC。它是尚未拆分的既有包级债务,本轮未宣称消除;`reviewedPackageCycles` 只冻结这一精确集合,新增节点或第二个环仍失败,价格簿查询与客户统计仍必须处于所有环之外。合同有新增消费者与新增环的负向测试。预约新增提供端通过原域适配接口消费事实,不增加该包环。
|