18 KiB
设计文档:Monorepo 迁移
概述
本设计将现有单一 ETL 仓库(FQ-ETL)迁移为 Monorepo 单体仓库(NeoZQYY),采用一次性搬迁策略。核心设计原则:
- 最小破坏性:ETL 整体平移,保持内部结构不变,仅调整外部引用
- 分层隔离:通过 uv workspace 实现 Python 包依赖隔离,通过
.env分层实现配置隔离 - 数据库重组:从现有 4 个 schema(billiards_ods/billiards_dwd/billiards_dws/etl_admin)重组为 6 层 schema(meta/ods/dwd/core/dws/app)
- 渐进式扩展:第一阶段只建必要骨架,未来扩展点记录在 Roadmap 中
架构
整体架构
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
数据流架构
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:
[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 为例):
[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 实现方案:
-- 创建应用角色
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 映射:
-- 在 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)
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 数据库
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 表定义迁移完整性
对于任意现有数据库 schema(billiards_ods、billiards_dws)中的表,新 schema(ods、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 次迭代
单元测试覆盖
- Scaffold 测试:验证目录创建、文件生成的具体示例
- 配置加载器测试:验证分层加载、冲突处理、缺失检测的边界情况
- 迁移脚本测试:验证文件复制、路径映射的具体场景
- 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,验证所有子项目依赖正确解析