Files
Neo-ZQYY/.kiro/specs/monorepo-migration/design.md

554 lines
18 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.
# 设计文档Monorepo 迁移
## 概述
本设计将现有单一 ETL 仓库(`FQ-ETL`)迁移为 Monorepo 单体仓库(`NeoZQYY`),采用一次性搬迁策略。核心设计原则:
1. **最小破坏性**ETL 整体平移,保持内部结构不变,仅调整外部引用
2. **分层隔离**:通过 uv workspace 实现 Python 包依赖隔离,通过 `.env` 分层实现配置隔离
3. **数据库重组**:从现有 4 个 schemabilliards_ods/billiards_dwd/billiards_dws/etl_admin重组为 6 层 schemameta/ods/dwd/core/dws/app
4. **渐进式扩展**:第一阶段只建必要骨架,未来扩展点记录在 Roadmap 中
## 架构
### 整体架构
```mermaid
graph TB
subgraph "NeoZQYY Monorepo"
subgraph "apps/"
ETL["apps/etl/pipelines/feiqiu/"]
Backend["apps/backend/ (FastAPI)"]
Mini["apps/miniprogram/ (Donut+TDesign)"]
Admin["apps/admin-web/ (未来)"]
end
subgraph "packages/"
Shared["packages/shared/"]
end
subgraph "gui/"
GUI["gui/ (PySide6过渡期)"]
end
subgraph "db/"
ETLDB["db/etl_feiqiu/"]
AppDB["db/zqyy_app/"]
FDW["db/fdw/"]
end
end
ETL --> Shared
Backend --> Shared
GUI --> Shared
Backend --> AppDB
ETL --> ETLDB
AppDB -.->|postgres_fdw 只读| ETLDB
```
### 数据流架构
```mermaid
graph LR
API["上游 SaaS API"] --> ODS["ods (原始数据)"]
ODS --> DWD["dwd (main+EX 明细)"]
DWD --> Core["core (统一最小字段集)"]
DWD --> DWS["dws (汇总/工资)"]
Core --> DWS
DWS --> App["app (视图+RLS)"]
App -.->|FDW 只读映射| ZqyyApp["zqyy_app DB"]
ZqyyApp --> FastAPI["FastAPI 后端"]
FastAPI --> MiniApp["微信小程序"]
subgraph "etl_feiqiu DB"
Meta["meta (调度/游标)"]
ODS
DWD
Core
DWS
App
end
```
## 组件与接口
### 1. 目录结构生成器Scaffold
负责创建 Monorepo 完整目录结构和基础配置文件。
**输入**:目标路径 `C:\NeoZQYY\`
**输出**:完整目录树 + README.md + 配置文件
**关键行为**
- 创建所有一级和二级目录
- 为每个一级目录生成 README.md作用 + 结构 + Roadmap
- 生成 `.gitignore``.kiroignore``.env.template`
- 初始化 Git 仓库
### 2. uv Workspace 配置
**根 `pyproject.toml`**
```toml
[project]
name = "neozqyy"
version = "0.1.0"
requires-python = ">=3.10"
[tool.uv.workspace]
members = [
"apps/etl/pipelines/feiqiu",
"apps/backend",
"packages/shared",
"gui",
]
```
**子项目 `pyproject.toml` 模式**(以 ETL 为例):
```toml
[project]
name = "etl-feiqiu"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"psycopg2-binary>=2.9.0",
"requests>=2.28.0",
"python-dateutil>=2.8.0",
"tzdata>=2023.0",
"python-dotenv",
"openpyxl>=3.1.0",
"neozqyy-shared",
]
[tool.uv.sources]
neozqyy-shared = { workspace = true }
```
### 3. 配置隔离机制
**分层加载顺序**
```
根 .env公共配置→ 应用 .env.local私有覆盖→ 环境变量 → CLI 参数
```
**实现方式**
- 现有 `AppConfig``DEFAULTS < ENV < CLI` 模式保持不变
- 新增:在 `load_env_overrides()` 中先加载根 `.env`,再加载应用级 `.env.local`
- 冲突策略:应用级优先(后加载覆盖先加载)
- 缺失检测:在 `_validate()` 中检查必需项,报告缺失项名称
### 4. ETL 平移策略
**平移范围**
| 源路径 | 目标路径 | 说明 |
|--------|----------|------|
| `api/` | `apps/etl/pipelines/feiqiu/api/` | API 客户端 |
| `cli/` | `apps/etl/pipelines/feiqiu/cli/` | CLI 入口 |
| `config/` | `apps/etl/pipelines/feiqiu/config/` | 配置 |
| `loaders/` | `apps/etl/pipelines/feiqiu/loaders/` | 加载器 |
| `models/` | `apps/etl/pipelines/feiqiu/models/` | 模型 |
| `orchestration/` | `apps/etl/pipelines/feiqiu/orchestration/` | 调度 |
| `scd/` | `apps/etl/pipelines/feiqiu/scd/` | SCD2 |
| `tasks/` | `apps/etl/pipelines/feiqiu/tasks/` | 任务 |
| `utils/` | `apps/etl/pipelines/feiqiu/utils/` | 工具 |
| `quality/` | `apps/etl/pipelines/feiqiu/quality/` | 质量检查 |
| `tests/` | `apps/etl/pipelines/feiqiu/tests/` | 测试 |
| `database/*.sql` | `db/etl_feiqiu/schemas/` | DDL |
| `database/migrations/` | `db/etl_feiqiu/migrations/` | 迁移脚本 |
| `database/seed_*.sql` | `db/etl_feiqiu/seeds/` | 种子数据 |
| `gui/` | `gui/` | GUI顶层 |
**import 路径策略**
- ETL 内部使用相对 import`from .config.settings import AppConfig`)或保持现有绝对 import
- `pyproject.toml` 中设置 `pythonpath`,使 `apps/etl/pipelines/feiqiu/` 为 Python 路径根
- `pytest.ini` 同步更新 `pythonpath = .`
- 目标ETL 内部代码零修改或最小修改
### 5. 小程序平移策略
**平移范围**
| 源路径 | 目标路径 |
|--------|----------|
| `C:\ZQYY\XCX\`(除 Prototype | `apps/miniprogram/` |
| `C:\ZQYY\XCX\Prototype\` | `docs/h5_ui/` |
小程序为独立前端项目Donut + TDesign不涉及 Python 依赖管理,直接复制即可。
### 6. 数据库 Schema 重组etl_feiqiu
**现有 → 新 schema 映射**
| 现有 Schema | 新 Schema | 说明 |
|-------------|-----------|------|
| `etl_admin` | `meta` | 调度、游标、运行记录 |
| `billiards_ods` | `ods` | ODS 原始数据,结构不变 |
| `billiards_dwd` | `dwd` | DWD 明细,保留 main+EX 拆分 |
| (新增) | `core` | 统一维度/事实最小字段集 |
| `billiards_dws` | `dws` | DWS 汇总,结构不变 |
| (新增) | `app` | 面向外部的视图/函数 + RLS |
**core schema 设计原则**
- 仅包含跨系统共享的最小字段集(如会员 ID、姓名、手机号、状态
- 维度表从 DWD 维度表提取核心字段
- 事实表从 DWD 事实表提取核心度量
- 第一版保持精简,后续按需扩展
**app schema 设计原则**
- 以视图VIEW封装 DWS/Core 层数据
- 所有视图启用 RLS`site_id` 过滤
- 提供函数接口供 FDW 映射使用
- 不存储实际数据,仅做访问层
**RLS 实现方案**
```sql
-- 创建应用角色
CREATE ROLE app_reader;
-- 在 app schema 的视图上启用 RLS
ALTER TABLE app.v_member_summary ENABLE ROW LEVEL SECURITY;
-- 创建策略:根据会话变量 app.current_site_id 过滤
CREATE POLICY site_isolation ON app.v_member_summary
FOR SELECT TO app_reader
USING (site_id = current_setting('app.current_site_id')::bigint);
```
### 7. 业务数据库设计zqyy_app
**核心表**
- `users`:用户账户(微信 OpenID、手机号、角色
- `roles` / `permissions`RBAC 权限模型
- `user_roles`:用户-角色关联
- `tasks`:任务管理(审批流)
- `approvals`:审批记录
**FDW 映射**
```sql
-- 在 zqyy_app 中创建外部服务器
CREATE SERVER etl_feiqiu_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', dbname 'etl_feiqiu', port '5432');
-- 创建用户映射
CREATE USER MAPPING FOR app_user
SERVER etl_feiqiu_server
OPTIONS (user 'app_reader', password '***');
-- 导入 app schema 的外部表
IMPORT FOREIGN SCHEMA app
FROM SERVER etl_feiqiu_server
INTO fdw_etl;
```
**约束**FDW 映射为只读,`zqyy_app` 不存储 ETL 数据副本。
### 8. FastAPI 后端骨架
**项目结构**
```
apps/backend/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 入口
│ ├── config.py # 配置加载
│ ├── database.py # 数据库连接
│ ├── routers/ # 路由模块
│ │ └── __init__.py
│ ├── middleware/ # 中间件
│ │ └── __init__.py
│ └── schemas/ # Pydantic 模型
│ └── __init__.py
├── tests/
│ └── __init__.py
├── pyproject.toml
└── README.md
```
**关键配置**
- 连接 `zqyy_app` 数据库(通过 FDW 访问 ETL 数据)
- OpenAPI 文档自动生成FastAPI 内置)
- 依赖 `packages/shared` 获取通用工具
### 9. 共享包packages/shared
**模块划分**
```
packages/shared/
├── src/
│ └── neozqyy_shared/
│ ├── __init__.py
│ ├── enums.py # 字段枚举定义
│ ├── money.py # 金额精度工具CNY, numeric(2)
│ └── datetime_utils.py # 时间处理工具
├── tests/
│ └── __init__.py
├── pyproject.toml
└── README.md
```
**提取来源**
- `enums.py`:从 ETL 的 `models/` 中提取通用枚举
- `money.py`:金额四舍五入、格式化(`Decimal` + `ROUND_HALF_UP`scale=2
- `datetime_utils.py`:时区转换、日期范围计算(从 `utils/` 提取)
### 10. .kiro 迁移
**迁移内容**
- 复制 `.kiro/steering/` 到 Monorepo
- 更新 `product.md`:从单一 ETL 视角扩展为 Monorepo 全局视角
- 更新 `tech.md`:新增 FastAPI、uv workspace、Donut+TDesign 等技术栈
- 更新 `structure-lite.md`:反映 Monorepo 目录结构和模块边界
- 更新路径引用:所有 steering 文件中的路径适配新结构
## 数据模型
### etl_feiqiu 数据库(六层 Schema
```mermaid
erDiagram
META {
bigint run_id PK
text task_code
timestamptz started_at
timestamptz ended_at
text status
jsonb result_summary
}
META ||--o{ ODS : "调度触发"
ODS {
bigint id PK
text content_hash PK
jsonb payload
text source_endpoint
timestamptz fetched_at
}
ODS ||--o{ DWD : "清洗装载"
DWD {
bigint id PK
timestamptz scd2_start_time
timestamptz scd2_end_time
int scd2_is_current
int scd2_version
}
DWD ||--o{ CORE : "提取最小字段集"
CORE {
bigint id PK
text name
bigint site_id
}
DWD ||--o{ DWS : "汇总聚合"
CORE ||--o{ DWS : "汇总聚合"
DWS {
bigint id PK
date stat_date
numeric amount
bigint site_id
}
DWS ||--o{ APP : "视图封装"
APP {
text view_name
text rls_policy
}
```
### zqyy_app 数据库
```mermaid
erDiagram
USERS {
bigint id PK
text wx_openid UK
text mobile
text nickname
int status
timestamptz created_at
}
ROLES {
int id PK
text name UK
text description
}
USER_ROLES {
bigint user_id FK
int role_id FK
}
PERMISSIONS {
int id PK
text resource
text action
}
ROLE_PERMISSIONS {
int role_id FK
int permission_id FK
}
USERS ||--o{ USER_ROLES : "拥有"
ROLES ||--o{ USER_ROLES : "分配给"
ROLES ||--o{ ROLE_PERMISSIONS : "包含"
PERMISSIONS ||--o{ ROLE_PERMISSIONS : "授予"
FDW_ETL_VIEWS {
text foreign_table_name
text source_schema
text mapping_type
}
```
### 配置分层模型
```
优先级(低 → 高):
┌─────────────────────────────┐
│ 根 .env公共配置模板 │ DB_HOST, DB_PORT, TIMEZONE
├─────────────────────────────┤
│ 应用 .env.local私有覆盖 │ DB_NAME, DB_PASSWORD, API_TOKEN
├─────────────────────────────┤
│ 环境变量 │ 运行时覆盖
├─────────────────────────────┤
│ CLI 参数 │ 最高优先级
└─────────────────────────────┘
```
## 正确性属性
*属性是系统在所有有效执行中都应保持为真的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性是人类可读规格与机器可验证正确性保证之间的桥梁。*
### Property 1: README.md 结构完整性
*对于任意* Monorepo 一级目录,其 README.md 文件应存在且包含"作用说明"、"结构描述"和"Roadmap"三个段落。
**Validates: Requirements 1.5**
### Property 2: Python 子项目配置完整性
*对于任意* uv workspace 声明的 Python 子项目成员,该子项目目录下应存在独立的 `pyproject.toml` 文件,且文件中包含 `[project]` 段落。
**Validates: Requirements 3.2**
### Property 3: 配置优先级 - .env.local 覆盖
*对于任意*配置项名称和两个不同的值,当根 `.env` 和应用 `.env.local` 都定义了该配置项时,配置加载器返回的值应等于 `.env.local` 中的值。
**Validates: Requirements 4.3**
### Property 4: 必需配置缺失检测
*对于任意*必需配置项,当所有配置层级(.env、.env.local、环境变量、CLI均未提供该项时配置加载器应抛出错误且错误信息中包含该缺失配置项的名称。
**Validates: Requirements 4.4**
### Property 5: 文件迁移完整性
*对于任意*源-目标目录映射关系ETL 业务代码、database 文件、tests 目录),源目录中的每个文件在目标目录的对应位置都应存在且内容一致。
**Validates: Requirements 5.1, 5.2, 5.3**
### Property 6: Schema 表定义迁移完整性
*对于任意*现有数据库 schemabilliards_ods、billiards_dws中的表新 schemaods、dws的 DDL 文件中应包含该表的 CREATE TABLE 定义。
**Validates: Requirements 7.3, 7.6**
### Property 7: Core schema 最小字段集
*对于任意* core schema 中的表,其字段数量应严格少于对应 dwd schema 中同名(或对应)表的字段数量。
**Validates: Requirements 7.5**
### Property 8: 测试数据库结构一致性
*对于任意*生产数据库etl_feiqiu、zqyy_app中的 schema 和表定义对应的测试数据库test_etl_feiqiu、test_zqyy_app中应存在相同的 schema 和表结构。
**Validates: Requirements 9.1, 9.2**
### Property 9: Steering 文件路径更新
*对于任意* `.kiro/steering/` 目录下的文件,文件内容中不应包含旧仓库路径引用(如 `FQ-ETL``C:\ZQYY\FQ-ETL`)。
**Validates: Requirements 10.2**
### Property 10: 业务表 site_id 字段存在性
*对于任意* app schema 中的业务视图和 dws/core schema 中的业务表,其定义中应包含 `site_id` 字段。
**Validates: Requirements 13.1**
### Property 11: RLS 按 site_id 隔离
*对于任意* app schema 中启用了 RLS 的视图,当会话变量 `app.current_site_id` 设置为某个门店 ID 时,查询结果应仅包含该 `site_id` 的数据行。
**Validates: Requirements 13.2**
## 错误处理
### 配置错误
- **缺失必需配置**:启动时立即报错,列出所有缺失项名称,不启动服务
- **配置值格式错误**:报告具体的配置项路径和期望格式
- **.env 文件不存在**:使用默认值继续,不报错(.env.template 仅为模板)
### 迁移错误
- **源文件不存在**:记录警告日志,继续迁移其他文件,最终汇总报告缺失文件列表
- **目标目录已存在**:提示用户确认是否覆盖,默认不覆盖
- **import 路径修复失败**:记录错误日志,标记需要手动修复的文件
### 数据库错误
- **Schema 创建失败**:回滚当前 schema 的所有 DDL报告失败原因
- **FDW 连接失败**:记录错误日志,不影响本地表的正常使用
- **RLS 策略创建失败**:回滚策略创建,报告受影响的表
### 测试数据库错误
- **结构不一致**:提供 diff 工具比较生产与测试库结构差异
- **数据迁移失败**:回滚到迁移前状态,报告失败的表和原因
## 测试策略
### 测试框架
- **单元测试**`pytest`Python 子项目)
- **属性测试**`hypothesis`Python 属性测试库)
- 每个属性测试配置最少 100 次迭代
### 单元测试覆盖
1. **Scaffold 测试**:验证目录创建、文件生成的具体示例
2. **配置加载器测试**:验证分层加载、冲突处理、缺失检测的边界情况
3. **迁移脚本测试**:验证文件复制、路径映射的具体场景
4. **DDL 语法测试**:验证生成的 SQL 语法正确性
### 属性测试覆盖
每个属性测试必须引用设计文档中的属性编号:
- **Feature: monorepo-migration, Property 1: README.md 结构完整性** — 验证所有一级目录 README 包含必需段落
- **Feature: monorepo-migration, Property 2: Python 子项目配置完整性** — 验证所有 workspace 成员有 pyproject.toml
- **Feature: monorepo-migration, Property 3: 配置优先级** — 生成随机配置项,验证 .env.local 覆盖行为
- **Feature: monorepo-migration, Property 4: 必需配置缺失检测** — 生成随机必需项组合,验证缺失报错
- **Feature: monorepo-migration, Property 5: 文件迁移完整性** — 验证源-目标文件映射的完整性
- **Feature: monorepo-migration, Property 6: Schema 表定义迁移完整性** — 验证现有表在新 DDL 中存在
- **Feature: monorepo-migration, Property 7: Core schema 最小字段集** — 验证 core 表字段数少于 dwd
- **Feature: monorepo-migration, Property 8: 测试数据库结构一致性** — 验证测试库与生产库结构相同
- **Feature: monorepo-migration, Property 9: Steering 文件路径更新** — 验证无旧路径残留
- **Feature: monorepo-migration, Property 10: 业务表 site_id 存在性** — 验证业务表包含 site_id
- **Feature: monorepo-migration, Property 11: RLS 隔离** — 验证 RLS 按 site_id 过滤(集成测试)
### 集成测试
- **ETL 运行验证**:在新目录结构下运行 `pytest tests/unit`,确保所有现有测试通过
- **数据库 Schema 验证**:在测试数据库上执行 DDL验证 schema 创建成功
- **FDW 连接验证**:验证 zqyy_app 通过 FDW 可读取 etl_feiqiu 的 app schema 数据
- **uv workspace 验证**:运行 `uv sync`,验证所有子项目依赖正确解析