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

18 KiB
Raw Blame History

设计文档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 中

架构

整体架构

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 参数

实现方式

  • 现有 AppConfigDEFAULTS < 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 内部使用相对 importfrom .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 层数据
  • 所有视图启用 RLSsite_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 / permissionsRBAC 权限模型
  • 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_UPscale=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 表定义迁移完整性

对于任意现有数据库 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-ETLC:\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 工具比较生产与测试库结构差异
  • 数据迁移失败:回滚到迁移前状态,报告失败的表和原因

测试策略

测试框架

  • 单元测试pytestPython 子项目)
  • 属性测试hypothesisPython 属性测试库)
  • 每个属性测试配置最少 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,验证所有子项目依赖正确解析