Files
baiyue-firstleg/firstleg/docs/firstleg-internal-modularization-baseline.md
T
2026-08-25 11:52:25 +08:00

190 lines
12 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.
# Firstleg 内部模块化基线与首切片方案
## 1. 范围与生成方式
本基线只分析 `firstleg` 同一部署单元,不拆微服务,不改变业务流程、数据库结构、OpenAPI 或跨服务契约。分析输入为:
- `src/main/java`:429 个 Java 文件;948 条可解析的模块内类依赖;66 个物理包。
- `src/test/java`:169 个 Java 文件。与主源码合并分析时共 598 个 Java 文件、1,635 条模块内类依赖、69 个物理包。
可重复命令:
```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/` | 4 | 1/0 | Java 依赖最低;唯一跨切片依赖为 Controller 调用 `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 个生产类,已集中在单一顶层包;没有业务切片对其产生 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`:验证基线无新增边,并锁定客户统计 4 类、唯一出向边、零入向边和不进入 SCC。
与其他架构专项的依赖:
- 专项一(拓扑与质量门禁):决定是否引入 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 -Dtest=CustomerTransitStatisticsServiceTest,CustomerTransitStatisticsContractTest -Dsurefire.failIfNoSpecifiedTests=false test
```
当前只新增只读脚本、合同和报告,没有 Java 签名变化;前两条命令是本轮直接门禁。第三条用于首切片实际迁移后的 Maven 行为验证。