visionA/docs/autoflow/04-architecture/db-integration-plan.md
jim800121chen 22f329cdf3 docs(architecture): DB 規劃淡化測試占比強調
測試是工時的自然組成,不再特別強調占比 / 與業界對照:
- 移除前言「測試占比」獨立小節,併入估算假設(中性敘述)
- §5 標題去掉「測試占比分析」,§5.3 精簡為各塊測試 hrs 明細
- 移除「約 45% vs 業界 25-35%」對照句
- 保留實用建議(要砍先砍邊界、別砍 integration 與回歸)
工時數字全未動。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:20:11 +08:00

450 lines
35 KiB
Markdown
Raw Permalink 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.

# visionA Backend 接資料庫 — 工時規劃文件Man-hours / Man-day 估算)
| 項目 | 內容 |
|------|------|
| **文件目的** | 提供 visionA-backend 各 in-memory store 接資料庫持久化的工時估算供主管對齊資源man-day、PM 排程、工程師排工使用。 |
| **適用範圍** | visionA-backend 6 個現有 in-memory store 的持久化規劃。不含 cluster / converter_job 兩張未 wire 的表、不含 prod 環境另建 DB見 §8 範圍外)。 |
| **狀態** | **規劃,未實作**。本文件為工時估算與技術規劃,非實作紀錄,無任何 production code 變更。 |
| **產出者** | Architect Agent |
| **最後更新** | 2026-06-16 |
| **數字基準** | 子任務級 man-hours1 人天 = 6.5 有效 hrs見 §2 估算假設)。 |
---
## Executive Summary給主管
**一句話結論**:把 visionA-backend 接 DB 的工程已被拆到子任務級並完成保守工時估算,**整體區間 2237.7 人天(視範圍)**;其中約 **80% 的 Go 端工作不需要等 DB 開好就能開工**,等 DB 連線資訊只卡最後 1.54 天的 stage 收尾驗證。
### 三種範圍的 man-day 區間
| 範圍 | 包含內容 | **Man-day** | 累計 Man-hours | 適用情境 |
|------|---------|-------------|---------------|---------|
| **最小可行(只 model** | DB 基礎建設 + 模型庫接 Postgres + 精簡驗證 | **7.8 13.5** | 51 88 | 使用者最關心:模型庫能持久化、重啟資料還在 |
| **持久資料** | 上述 + device + pairing/session token | **16 27.4** | 104 178 | 所有業務持久資料上 Postgressession 暫留 in-memory |
| **完整** | 上述 + session 接 Redis + 交易/韌性 | **22 37.7** | 143 245 | 全部持久化 + Redis session + 交易/韌性 |
> 區間下限 = 一切順利;上限 = 含 schema 細節踩坑與測試補齊。三種範圍皆已含「DB 基礎建設」與「一次 stage 驗證」。
> 各塊估算已含接 DB 所需的三層測試unit、integrationtestcontainers 真 DB、併發/連線失敗等邊界,以及既有 e2e 的回歸驗證——這些是接 DB 的必要工程組成,詳見 §5。
### 兩個關鍵結論
1. **DB 由他人開好、Go 端不受影響**DB 機器與 visionA 專用 database 由他人在 stage host130開好並提供連線資訊。visionA 端**不負責 provision DB**,但 Go 端所有工作連線池、config、migration 撰寫、repository 實作、testcontainers 整合測試一小時都沒省——這些跟「DB 誰開」無關。整合測試改用 testcontainers本機/CI 一次性 DB不依賴 130更可靠。
2. **「等 DB」只卡最後收尾**:拿到 DB 連線資訊**前**最小範圍可做到「testcontainers 全綠」的程度(約 6.511 個工作天的活),只差最後 stage 接上驗證1.52.5 天)。換句話說,**只要範圍拍板,工程師可立刻開工,不會被「等 DB」block 大部分工作。**
---
## 1. 背景與前提
### 1.1 為什麼這次接 DB 估得動
6 個 store 全部已有 `Repository` / `Store` interface + in-memory 實作,`main.go` 裡是乾淨的 6 個 `NewInMemory*` 呼叫點。接 DB = 加 Postgres/Redis 實作 + 換 1 行 wiring**interface 與所有 handler 不用動**。這是估算偏可控的根本原因。
### 1.2 從零起手的部分(誠實交代)
`go.mod` 目前**沒有**任何 DB 依賴pgx / redis / golang-migrate 皆無),`migrations/` 目錄不存在,`config.go` 無 DB 設定區塊。因此「DB 基礎建設」(塊 0是真的從零建連線池、config、migration 工具、CI Postgres。
此外pairing / session-token 兩個 store 目前用 **plaintext 當 map key**,接 Postgres 要改成 **token_hash 當 PK**`database.md` §2.4 已是此意圖code 已有 `HashToken()`)。這是邏輯改動、不是純 swap塊 3 須特別小心。
### 1.3 ⚡ 關鍵前提DB 由他人在 130 另開visionA 不負責 provision
> **此前提很重要避免讀者誤解「DB 現成可省一大塊」。**
- stage host 192.168.0.130 是整個 stage 的 docker hostvisionA / MC / FAA / converter / minio 都在上面)。
- 130 上已存在的 DB container 都屬於其他服務 / 專案visionA 一個都不該共用。
- 130:5432 port 雖 OPEN 但**不對應任何已知 container**(來源不明),**不採信為 visionA 可用**。
-**已確認**:會有人在 130 上幫 visionA **另外開好專用 DB**Postgres + 視需要 Redis開好後提供連線資訊。
**對工時的影響**
| 項目 | visionA 端是否要做 | 說明 |
|------|-------------------|------|
| Provision DB 機器 / 起 container | **不用**(他人做) | 省約 612 hrs |
| 建 visionA database / 角色權限 | **不用**(他人做,含在「給連線資訊」裡) | 省約 24 hrs |
| 連線池 / config / migration 撰寫 | **照樣要做** | 跟 DB 誰開無關 |
| Repository 實作 + 整合測試 | **照樣要做** | 改用 testcontainers不依賴 130 |
→ 一句話:**省下的是「DB provisioning + database 建置 + 130 維運邊界」(約 1020 hrs / 1.53 人天Go 端全部照做。** 整合測試改用 testcontainers本機/CI 一次性 DB這比連 130 測試更可靠CI 能跑、隔離乾淨),但 testcontainers 設置成本(約 917 hrs如實算進塊 0。淨效果最小範圍總工時主要被「接 DB 必要的完整測試」佔比推高不是被「DB 誰開」推高。
---
## 2. 估算單位與假設(讓數字可信)
| 項目 | 設定 | 理由 |
|------|------|------|
| **基本單位** | man-hours人時子任務加總 → 塊小計 → 範圍累計 | 細到子任務才能排工 |
| **Man-day 換算** | **1 人天 = 6.5 有效 hrs**(非 8 | 扣掉 context switch、PR 溝通、等 CI、開會、被打斷的真實 overhead |
| **人力假設** | 1 位熟 Go、熟本 codebase 的 backend 工程師 | interface 已在、有現成 in-memory 測試可對照 |
| **hours 已含** | 實作 + 三層測試 + 一輪 code review 修正 | 每塊獨立列「self-review + 過 Reviewer」子任務 |
| **hours 不含** | 跨團隊等待(拿連線資訊、他人開 DB、需求變更、prod 另建 DB、CI 大改造意外 | 這些不可控、不放進工程估算 |
| **區間語意** | 下限 = 一切順利;上限 = 含 schema 細節array、unique×soft-delete、hash key 切換)踩坑與測試補齊 | |
| **測試範圍** | 含 integrationtestcontainers+ 邊界 + 回歸 | 接 DB 的必要工程成本,已含於各塊估算,詳見 §5 |
> **塊 0 第一次做最貴**連線池、migration 機制、testcontainers、CI Postgres 都從零。完成後塊 13 每塊攤平、可偏區間低值。
---
## 3. DB 選型分工
對齊 `database.md` §2.7「Session 不落 DB、Phase 1 考慮 Redis」的既有立場。
### 3.1 放 PostgreSQL持久業務資料
| Store | package | 理由 |
|-------|---------|------|
| `modelRepo` | `internal/model` | 模型庫要長期保存、要查詢List by owner/chip/source、跨重啟不能掉。**使用者最關心。** |
| `deviceRepo` | `internal/device` | 裝置綁定身分長期保存、有 owner/serial unique 約束、有關聯查詢需求。 |
| `pairingStore` | `internal/auth` | 配對 token 是「可撤銷的長期憑證」要稽核used_at/revoked_at重啟不能掉。 |
| `sessionTokenStore` | `internal/auth` | session token 90 天長效、可撤銷、要查 parent token 稽核鏈。 |
### 3.2 放 Redis揮發 session有 TTL、高頻讀寫
| Store | package | 理由 |
|-------|---------|------|
| `userSessionStore` | `internal/usersession` | 瀏覽器 cookie session。高頻讀、有 idle/absolute TTL、掉了重登即可。Redis TTL 原生支援。 |
| remote-proxy `session` | `internal/session` | tunnel session。**特例handle 是 process-local 活連線物件yamux/WS不能序列化進 Redis。** 單節點維持 in-memory多節點才需 Redis 存 Summaryhandle 仍留本地)。 |
### 3.3 為什麼不全放 Postgres / 不全放 Redis
- **不全放 Postgres**cookie session 高頻讀寫 + 自然過期Postgres 會變熱點、還要自己寫 cleanup jobRedis TTL 天生適配。
- **不全放 Redis**model/device/token 要關聯查詢、unique 約束、稽核、長期保存Redis 做這些彆扭。
- **結論**:持久業務資料 → Postgres揮發 session → Redis`database.md` 既有立場一致。
---
## 3.5 MySQL 版本估算(並列補充,不取代 PG 版)
> 本節為「**若改用 MySQL 取代 PostgreSQL 作為持久業務資料庫**」的並列工時估算,供主管在 DB 選型上對齊。**§4 各塊與 §5 三範圍的數字仍以 PostgreSQL 為基準、不受本節影響**;本節只估「換 MySQL 相對 PG 額外多出的工時delta並據此推出 MySQL 版的三範圍累計。Redis 部分(塊 4與 DB 選型無關delta = 0。
### 3.5.1 為什麼會有 delta現有 schema 用了 5 項 PG 專屬特性
現有 `database.md` §4 的 6 張表 schema 是針對 PostgreSQL 設計的,以下 5 項是 **PG 原生、MySQL 沒有或需另解**的特性,換 MySQL 每一項都要改 schema + repository code + 測試:
| # | PG 特性 | 用在哪 | MySQL 對應做法 | 影響面 |
|---|---------|--------|---------------|--------|
| 1 | `gen_random_uuid()` UUID 主鍵 | 6 張表全用users/devices/models/pairing_tokens/clusters/converter_jobs | `BINARY(16)` + app 端產 UUID推薦省空間`CHAR(36)` + `UUID()` | migration + repository 掃描/綁定欄位邏輯 |
| 2 | `CITEXT`(大小寫不敏感唯一) | users.email | `VARCHAR` + collation`utf8mb4_0900_ai_ci` | migrationusers 為 Phase 1 stub影響小 |
| 3 | `TEXT[]` array 欄位 | users.roles、models.classes、models.input_shape`INT[]` | **MySQL 無原生 array**`JSON` 欄位MySQL 8+ repository 序列化/反序列化 | **delta 最大來源**,集中在塊 1 |
| 4 | `JSONB` | clusters.devices_json、params | MySQL `JSON`(無 JSONB binary 索引優化,功能足夠) | 本期 cluster 未 wire§8 範圍外),實際影響小 |
| 5 | partial index `WHERE deleted_at IS NULL` | devices/models 軟刪唯一索引 | **MySQL 8 不支援 partial index** → functional index 或全欄索引(略低效)或調整查詢 | 塊 1、塊 2 的 unique×soft-delete 語意 |
> 補充:本期實際 wire 的 6 個 store 中array 特性集中在 **model塊 1的 classes + input_shape** 與 users.rolesusers 為 Phase 1 stubJSONB 在 cluster範圍外。因此 delta 重心落在塊 1其次塊 2/3 的 UUID 與 partial unique。
### 3.5.2 各功能塊 PG vs MySQL 工時對照
下表以 §4/§5 各塊的 PG 區間為基準,列出 MySQL 版的額外 deltahrs順利卡關與成因。delta 已含「對應的 schema 改動 + repository code + 測試補齊」:
| 塊 | 功能 | PG hrs | MySQL delta | MySQL hrs | delta 主因 |
|----|------|--------|-------------|-----------|-----------|
| 0 | DB 基礎建設 | 2036 | **+47** | 2443 | Go driver 換pgx→`go-sql-driver/mysql` 或 GORM、migration 工具 MySQL dialect、testcontainers 換 MySQL image + helper 調整 |
| 1 | **model 接 DB** | 2236 | **+59** | 2745 | `classes`/`input_shape` 兩個 array→`JSON` 欄位 + 序列化/反序列化 + round-trip 測試重打UUID 綁定partial index 替代functional index / 查詢調整) |
| 2 | device 接 DB | 1932 | **+35** | 2237 | UUID 綁定;`UNIQUE(owner,serial)`×soft-delete 的 partial unique 替代MySQL 8 無 partial index雙狀態欄位 round-trip |
| 3 | pairing + session token | 3049 | **+24** | 3253 | UUID 處理(`BINARY(16)`/`CHAR(36)`token_hash PK 在 MySQL 的型別/索引處理hash 切換邏輯本身不變) |
| 4 | session 接 Redis | 1932 | **+0** | 1932 | Redis 與 DB 選型無關 |
| 5 | 一致性/交易/韌性 | 2035 | **+24** | 2239 | **交易隔離級別預設不同**PG 預設 Read Committed、MySQL InnoDB 預設 Repeatable Read → 一次性 token MarkUsed、cascade 撤銷等併發行為需重新驗證(含 gap lock 行為差異) |
| 6 | stage 部署 + e2e 回歸 | 1325 | **+12** | 1427 | MySQL 版 migration 跑 + 版本特性確認JSON 型別、collation、functional index 支援,取代 PG 的 `gen_random_uuid()`/CITEXT 確認) |
| | **全部加總** | 143245 | **+1731** | **160276** | |
> delta 區間語意同主表:下限 = array/UUID/index 替代一次到位;上限 = JSON 序列化邊界、隔離級別行為差異、functional index 效能調校踩坑。測試估算框架沿用 §2/§5 既有原則(接 DB 的客觀整合/邊界/回歸測試需要delta 中已含對應測試補齊、未額外灌入測試。
### 3.5.3 MySQL 版三種範圍累計(與 PG 版並列)
沿用 §5.2 的三範圍定義(含塊組成、塊 6 計法一致),套上各塊 MySQL hrs
| 範圍 | 包含塊 | PG 累計 hrs | PG Man-day | **MySQL 累計 hrs** | **MySQL Man-day** |
|------|--------|------------|-----------|-------------------|-------------------|
| **最小可行(只 model** | 0 + 1 + 6 精簡 | 5188 | 7.813.5 | **60102** | **9.215.7** |
| **持久資料** | 0 + 1 + 2 + 3 + 6 完整 | 104178 | 1627.4 | **119204** | **18.331.4** |
| **完整** | 0+1+2+3+4+5+6 | 143245 | 2237.7 | **160276** | **24.642.5** |
> 換算同 §21 人天 = 6.5 有效 hrs。最小範圍塊 6 取精簡值6.16.4,含 MySQL +12 delta中間範圍不重複計塊 6。
### 3.5.4 結論MySQL 比 PG 多多少、值不值得
**整體 delta**MySQL 版相對 PG 版多 **+1731 hrs約 +2.64.8 人天)**,以各範圍中位推算約 **+11%~13%**
- 最小範圍:+914 hrs約 +18%,因基數小、塊 0+1 的 driver/array delta 占比被放大)
- 持久資料範圍:+1526 hrs約 +14%
- 完整範圍:+1731 hrs約 +12%
→ 比先前粗估的「+10~20%」更收斂:**範圍越大、delta 百分比越低**(基礎建設與 array 的一次性 delta 被攤平),落在 **+11%~18%** 之間,整體中位約 **+13%**。
**主要 delta 來源排序**
1. **塊 1model array→JSON** — 最大,+59 hrs`classes` + `input_shape` 兩個 array 欄位都要改 JSON 序列化。
2. **塊 0driver/工具鏈換)** — +47 hrs一次性。
3. **塊 2UUID + partial unique 替代)** — +35 hrs。
4. **塊 5隔離級別行為驗證** — +24 hrs易被低估但攸關 token 一次性正確性。
**中立建議(該不該換 MySQL**
-**若團隊已有 MySQL 維運能力、或公司技術標準就是 MySQL** → 這 +13% 的開發 delta 多半值得,因為能省下 PG 的維運學習成本、監控/備份工具重建、DBA 熟悉度等**長期維運成本**(這部分不在本工時表內,但實務上往往大於一次性的 +2.64.8 人天)。
- ⚠️ **若無特殊原因(團隊對兩者都不熟、或本來就用 PG** → 對「這個 schema」而言 **PostgreSQL 更自然**arrayclasses/input_shape、JSONBcluster params、partial indexsoft-delete unique三項都是 PG 原生、零 delta換 MySQL 等於用額外工時把這些特性「降級模擬」回來。
- **本文件立場(中立)**:純就「現有 schema 的契合度」與「一次性開發成本」看PG 略優;但 DB 選型應由**維運能力與組織標準**主導,本節提供的是「換 MySQL 的開發成本帳」,不是選型結論。
---
## 4. 各功能塊子任務拆解 + man-hours
> 標記:🟢 = 拿到 DB 連線資訊前就能做testcontainers 不依賴 130🔵 = 必須等 DB 連線資訊 / 真 DB 才能驗。
### 塊 0DB 基礎建設(所有 Postgres 塊的硬前置)
| # | 子任務 | hrs順利卡關 | 依賴 | 標記 |
|---|--------|----------------|------|------|
| 0.1 | 加依賴 `pgx/v5`pgxpool+ `golang-migrate/v4`go.mod/go.sum 整理 | 12 | — | 🟢 |
| 0.2 | `config.DatabaseConfig`DSN/host/port/user/password/dbname/sslmode/pool size+ env 解析 + `Enabled()` 模式 + `.env.example` | 23 | 0.1 | 🟢 |
| 0.3 | 連線池 `internal/db/pool.go`pgxpool 建池 + 啟動 ping + graceful shutdown | 24 | 0.1,0.2 | 🟢/🔵 |
| 0.4 | migration runner`migrations/` + 啟動自動 migrate up 或獨立 `cmd/migrate`+ 第一份骨架 migration | 35 | 0.1 | 🟢 |
| 0.5 | main.go wire 連線池(依 config 決定是否建池,本塊只接骨架) | 12 | 0.3 | 🟢 |
| 0.6(測試) | **整合測試基礎建設**testcontainers-go 設置 + 測試 helper`setupTestDB(t)` 自動 migrate + truncate+ fixture/factory builder | 47 | 0.1,0.4 | 🟢 |
| 0.7(測試) | **CI 接 Postgres**CI workflow 讓 testcontainers 在 CI 跑Docker-in-CI+ migration 跑通 + cache 調校 | 36 | 0.6 | 🟢 |
| 0.8(測試) | 連線池/migration 本身測試ping、migrate up/down 冪等、池耗盡、連線失敗 fail-fast | 24 | 0.6 | 🔵 |
| 0.9 | 整塊 self-review + 過 Reviewer + 修正 | 23 | 全部 | 🟢 |
| | **塊 0 小計** | **2036** | | |
> 一次性最貴投資。「DB 由他人開好」讓 0.3/0.8 不用煩惱在 130 上裝/起 DB但 testcontainers0.6)成本如實算入。
### 塊 1model 接 Postgres ← 使用者最關心
| # | 子任務 | hrs順利卡關 | 依賴 | 標記 |
|---|--------|----------------|------|------|
| 1.1 | `PostgresModelRepository`Get/List(by owner/chip/source)/Save(upsert ON CONFLICT)/Delete(soft) | 46 | 塊0 | 🟢 |
| 1.2 | `input_shape INT[]` / `classes TEXT[]` pgx array 映射 + `Source` enum + upsert 保留 CreatedAt 語意 | 24 | 1.1 | 🟢 |
| 1.3 | migration `create_models`(含 `faa_object_key TEXT` nullable — `database.md` §4 漏此欄indexowner/chip/source、`deleted_at IS NULL` partial | 23 | 塊0 | 🟢 |
| 1.4 | main.go wiring 切換config 決定 Postgres/in-memory保留 in-memory 給 local dev+ `seedDemoData` 改寫進 DB | 23 | 1.1,塊0 | 🟢 |
| 1.5(測試) | **unit/邏輯**:對齊既有 `inmemory_repository_test.go`SaveAndGet、NotFound、List 三 filter、soft delete、Save 需 ID→ Postgres 版重打 | 35 | 1.1 | 🟢 |
| 1.6(測試) | **integration/真 DB**array round-trip、upsert 保留 CreatedAt、soft-delete 後 List 不含、List filter SQL 正確性、faa_object_key nullable round-trip | 47 | 1.1,0.6 | 🟢 |
| 1.7(測試) | **邊界**:空 List、重複 Save、併發 Save 同 ID、連線中斷 Get 行為、context cancel | 35 | 1.1,0.6 | 🟢 |
| 1.8 | 整塊 self-review + 過 Reviewer + 修正 | 23 | 全部 | 🟢 |
| | **塊 1 小計** | **2236** | | |
> model 欄位最多array + FAA 欄位 + 3 維 filter測試子任務1.51.7)合計 1017 hrs偏重。
### 塊 2device 接 Postgres
| # | 子任務 | hrs順利卡關 | 依賴 | 標記 |
|---|--------|----------------|------|------|
| 2.1 | `PostgresDeviceRepository`Get/GetBySerial/List(by owner)/Save(upsert)/Delete(soft) | 35 | 塊0 | 🟢 |
| 2.2 | migration `create_devices`(雙狀態欄位 remote_status/last_seen_at/last_connected_at + `UNIQUE(owner_user_id, serial_number)` | 23 | 塊0 | 🟢 |
| 2.3 | `UNIQUE(owner,serial)` × soft-delete 語意(已刪 serial 能否重註冊 → partial unique index `WHERE deleted_at IS NULL`+ 決策註記 | 24 | 2.2 | 🟢 |
| 2.4 | main.go wiring 切換 + `seedDemoData` device | 12 | 2.1,塊0 | 🟢 |
| 2.5(測試) | **unit/邏輯**:對齊既有 device testSaveAndGet、GetBySerial 跨 owner 不串、List by owner、soft delete、再刪回 NotFound、保留 CreatedAt | 35 | 2.1 | 🟢 |
| 2.6(測試) | **integration/真 DB**unique 衝突、partial unique 讓已刪 serial 可重註冊、雙狀態欄位 round-trip、upsert 保留 CreatedAt | 46 | 2.1,0.6 | 🟢 |
| 2.7(測試) | **邊界**:空 List、併發註冊同 serial、連線中斷、context cancel | 24 | 2.1,0.6 | 🟢 |
| 2.8 | 整塊 self-review + 過 Reviewer + 修正 | 23 | 全部 | 🟢 |
| | **塊 2 小計** | **1932** | | |
### 塊 3pairing_token + session_token 接 Postgres
| # | 子任務 | hrs順利卡關 | 依賴 | 標記 |
|---|--------|----------------|------|------|
| 3.1 | `PostgresPairingStore`Create/Validate/MarkUsed/Revoke/List/CleanupExpired | 46 | 塊0 | 🟢 |
| 3.2 | `PostgresSessionTokenStore`Create/Get/Revoke/CleanupExpired含 parent_token_hash 稽核鏈) | 35 | 塊0 | 🟢 |
| 3.3 | **關鍵改動**plaintext→token_hash 當 PK。Validate/Get 先 `HashToken()` 再查;確保所有呼叫端傳 plaintext 進、內部一致 hash | 35 | 3.1,3.2 | 🟢 |
| 3.4 | migration `create_pairing_tokens`pairing + session token 是否共表 by `kind` — 需決策,傾向共表;含 used_at/revoked_at/expires_at/parent_token_hash + index | 24 | 塊0 | 🟢 |
| 3.5 | main.go wiring 切換(兩個 store+ `seedDemoData` pairing token | 23 | 3.1,3.2,塊0 | 🟢 |
| 3.6(測試) | **unit/邏輯pairing**:對齊既有 testCreateAndValidate、unknown token、MarkUsed 一次性+冪等、Revoke、CleanupExpired、List by user、Validate expired | 35 | 3.1 | 🟢 |
| 3.7(測試) | **unit/邏輯session token**:對齊既有 testCreateAndGet、NotFound、expired、Revoke 冪等、Revoke NotFound、CleanupExpired、NeverExpires ttl=0 | 35 | 3.2 | 🟢 |
| 3.8(測試) | **integration/真 DB**hash 當 PK 查詢正確性、TTL 過期、一次性 used 的 DB 層 race兩併發 MarkUsed 只一成功)、撤銷稽核欄位、共表 kind 隔離 | 58 | 3.1,3.2,0.6 | 🟢 |
| 3.9(測試) | **邊界**:併發 Validate 同 token、CleanupExpired 大量資料、連線中斷、context cancel | 24 | 0.6 | 🟢 |
| 3.10 | 整塊 self-review + 過 Reviewer + 修正 | 34 | 全部 | 🟢 |
| | **塊 3 小計** | **3049** | | |
> Postgres 三塊裡最重:兩個 store + plaintext→hash 邏輯切換(漏一個呼叫端就驗不過)+ 一次性語意的 DB 層併發正確性測試。測試子任務3.63.9)合計 1322 hrs反映「token 語意錯會出安全問題」的保守估法。
>
> **加註(不在本塊預設範圍)**remote-proxy 目前只驗 token 格式不查 store。若 Phase 1 要新增 `GET /internal/session-token/:token` 給 remote-proxy 拉驗證 → 另 **+47 hrs**,不含在塊 3 小計。
### 塊 4session 接 RedisuserSessiontunnel session 維持 in-memory
> ⚠️ 此塊需 Redis。若本期不做塊 4可叫他人**先不用開 Redis**,只開 Postgres。測試用 miniredis純 Go in-process或 testcontainers Redis不依賴 130。
| # | 子任務 | hrs順利卡關 | 依賴 | 標記 |
|---|--------|----------------|------|------|
| 4.1 | 加依賴 `redis/go-redis/v9` + `config.RedisConfig`host/port/password/db index+ env + `.env.example` + `Enabled()` | 23 | — | 🟢 |
| 4.2 | `internal/db/redis.go` 連線 + 啟動 ping + graceful close | 13 | 4.1 | 🟢/🔵 |
| 4.3 | `RedisUserSessionStore`Create/Get/Update/Delete/CleanupExpired用 Redis TTLidle + absolute 雙 TTL取代手動 cleanup goroutineExtra map JSON 序列化 | 46 | 4.1,4.2 | 🟢 |
| 4.4 | main.go wiring 切換 + 移除/停用 `runUserSessionCleanup` goroutine改靠 Redis TTL | 12 | 4.3 | 🟢 |
| 4.5(測試) | **unit/邏輯**:對齊既有 usersession testCreateAndGet、NotFound/EmptyID、Get 回副本、Update 移 LastSeenAt 不動 CreatedAt、Update NotFound/Nil、Delete 冪等、Extra round-trip、context cancel | 46 | 4.3 | 🟢 |
| 4.6(測試) | **integration/真 Redis**TTL 實際過期、idle vs absolute 雙 timeout、序列化 round-trip、key 命名/隔離 | 35 | 4.3 | 🟢 |
| 4.7(測試) | **邊界 + 併發**race detector、Redis 連線中斷 store 行為、TTL 邊界 | 24 | 4.3 | 🟢 |
| 4.8 | 整塊 self-review + 過 Reviewer + 修正 | 23 | 全部 | 🟢 |
| | **塊 4 小計** | **1932** | | |
> tunnel session`internal/session`value 是活的 yamux Handle 不可序列化 → 維持 in-memory單節點本塊不動。跨節點「Summary 放 Redis、handle 留本地」列範圍外(要的話另估 +1326 hrs ≈ 24 人天)。
### 塊 5一致性 / 交易 / 連線韌性 / 健康檢查
| # | 子任務 | hrs順利卡關 | 依賴 | 標記 |
|---|--------|----------------|------|------|
| 5.1 | `internal/db/tx.go` 交易 helperpgx tx 包裝 + rollback on error + context | 24 | 塊0 | 🟢 |
| 5.2 | 跨 store 交易:建 Device + PairingToken 同 tx、刪 Device cascade 撤銷 token、converter job 完成→建 model transactional upsert | 47 | 5.1,塊1-3 | 🟢 |
| 5.3 | 連線韌性pool retry / context timeout / 斷線重連 / fail-fast vs degrade 策略(策略需使用者裁決) | 36 | 塊0 | 🟢 |
| 5.4 | `/healthz` 擴充 DB/Redis ping + 統一 pgx error → `internal/api/errors.go` 映射 | 24 | 塊0 | 🟢 |
| 5.5(測試) | **交易測試**rollback中途失敗整筆回滾、cascade 撤銷 token 交易邊界、併發交易 | 46 | 5.2,0.6 | 🟢 |
| 5.6(測試) | **韌性測試**連線中斷模擬、timeout、健康檢查回應DB down 時 /healthz 行為) | 35 | 5.3,5.4,0.6 | 🟢 |
| 5.7 | 整塊 self-review + 過 Reviewer + 修正 | 23 | 全部 | 🟢 |
| | **塊 5 小計** | **2035** | | |
> 區間寬反映「做到多嚴謹」的彈性:最小只做 /healthz + 基本 tx取下限完整韌性策略retry/degrade/cascade 全做)取上限。
### 塊 6stage 部署接 130 + e2e 回歸驗證
| # | 子任務 | hrs順利卡關 | 依賴 | 標記 |
|---|--------|----------------|------|------|
| 6.1 | 拿到他人開好的 DB 連線資訊後:`.env.stage` 注入 DSN/憑證(走既有 secrets 機制,不進 repo | 12 | DB 連線資訊 | 🔵 |
| 6.2 | 在 130 的 visionA database 跑 migration + 確認 PG 版本支援 `gen_random_uuid()`/CITEXT不支援則調 migration | 24 | 6.1 | 🔵 |
| 6.3 | stage 部署設定 / compose / CI 部署步驟接上 DB env | 23 | 6.1 | 🔵 |
| 6.4(測試) | **e2e 持久化驗證**:登入(OIDC)→配對→上傳 model→列 model→**重啟 backend→資料還在**(核心驗收)+ 空庫/seed 啟動正常 | 35 | 6.2,6.3 | 🔵 |
| 6.5(測試/回歸) | **回歸測試**:接 DB 後既有 e2e/integration約 6+ 檔)重跑,修因持久化行為改變而壞的測試 | 48 | 6.2 | 🔵/🟢 |
| 6.6 | 部署驗證彙報 + 修 stage 特有問題 | 13 | 全部 | 🔵 |
| | **塊 6 小計** | **1325** | | |
> 6.5 回歸測試刻意估寬48 hrs既有 6+ 個 e2e/integration 測試檔,接 DB 後「重啟資料還在」「seed 行為」「token hash 切換」都可能讓 in-memory 假設失效,要逐一確認。**這是接 DB 回歸驗證的必要成本。**
---
## 5. 工時總表
### 5.1 各塊 man-hours 總表
| 塊 | 功能 | hrs順利卡關 | 換算 Man-day÷6.5 | DB | 主要依賴 |
|----|------|----------------|---------------------|-----|---------|
| 0 | DB 基礎建設(連線池/config/migration/testcontainers/CI | 2036 | 3.15.5 | PG | — |
| 1 | **model 接 Postgres含 FAAObjectKey** | 2236 | 3.45.5 | PG | 塊0 |
| 2 | device 接 Postgres | 1932 | 2.94.9 | PG | 塊0 |
| 3 | pairing + session token 接 Postgres | 3049 | 4.67.5 | PG | 塊0 |
| 4 | session 接 RedisuserSession | 1932 | 2.94.9 | Redis | —與塊0平行 |
| 5 | 一致性/交易/韌性/健康檢查 | 2035 | 3.15.4 | PG | 塊13 |
| 6 | stage 部署接 130 + e2e 回歸驗證 | 1325 | 2.03.8 | PG+Redis | 想驗的塊 |
| | **全部加總** | **143245** | **2237.7** | | |
### 5.2 三種範圍累計
| 範圍 | 包含塊 | 累計 hrs | Man-day | 說明 |
|------|--------|---------|---------|------|
| **最小可行(只 model** | 0 + 1 + 6 精簡(只跑 6.16.4 約 916 hrs | **5188** | **7.813.5** | 模型庫持久化 + 驗證重啟資料還在。塊 0 是硬前置。 |
| **持久資料** | 0 + 1 + 2 + 3 + 6 完整 | **104178** | **1627.4** | 業務持久資料上 Postgres。session 仍 in-memory內測可接受。 |
| **完整** | 0+1+2+3+4+5+6 | **143245** | **2237.7** | 全部持久化 + Redis session + 交易/韌性。tunnel 多節點 summary 範圍外(另 +1326 hrs。 |
> 三種範圍皆已含塊 0 與一次塊 6 驗證;中間範圍不重複計塊 6。最小範圍的塊 6 只跑 6.16.4(不含完整回歸 6.5),故取精簡值。
### 5.3 各塊測試子任務 hrs
| 塊 | 測試子任務 | 測試 hrs | 該塊總 hrs |
|----|-----------|---------|-----------|
| 0 | 0.6+0.7+0.8 | 917 | 2036 |
| 1 | 1.5+1.6+1.7 | 1017 | 2236 |
| 2 | 2.5+2.6+2.7 | 915 | 1932 |
| 3 | 3.6+3.7+3.8+3.9 | 1322 | 3049 |
| 4 | 4.5+4.6+4.7 | 915 | 1932 |
| 5 | 5.5+5.6 | 711 | 2035 |
| 6 | 6.4+6.5 | 713 | 1325 |
> testcontainers/CI 一次性基礎建設(塊 0 的 917 hrs做一次、後續所有塊共用。若日後評估要砍工時最先可砍各塊「邊界」子任務的卡關上限但**不建議砍 integration 與回歸**(那是「重啟資料還在」的核心保證)。
---
## 6. 可先做(不卡 DBvs 等 DB 的劃分
### 6.1 拿到 DB 連線資訊前「就能先做」(🟢,約 80% 工作)
- **塊 0**加依賴、config、連線池建池邏輯、migration runner+骨架、main.go 接骨架、testcontainers 設置、CI Postgres。
- **塊 1/2/3****全部 repository 實作 + migration 撰寫 + 全部測試unit + integration via testcontainers + 邊界)**。testcontainers 自己起一次性 DB完全不依賴 130。
- **塊 4**全部miniredis / testcontainers Redis
- **塊 5**tx helper、韌性邏輯、health 擴充、交易測試testcontainers
**拿到連線資訊前,最小範圍(塊 0+1可做到「testcontainers 全綠」(約 6.511 個工作天的活),只差最後 stage 接上驗證。**
### 6.2 必須等 DB 連線資訊 / 真 DB🔵
- **塊 0**0.3 連真 DB ping 確認、0.8 對真 DB 測試(可先用 testcontainers 跑、再對 stage DB 確認一次)。
- **塊 6****整塊**(注入憑證、在 130 跑 migration、部署設定、e2e、stage 回歸、彙報)—— 這是唯一硬卡 DB 連線資訊的塊。
---
## 7. 排程建議(假設 1 個 backend 全職)
### 7.1 依賴關係(哪些可平行)
```
塊0基礎建設──┬──► 塊1model──┐
├──► 塊2device─┤
├──► 塊3token──┼──► 塊5交易/韌性需塊1-3
塊4Redis─────與塊0平行獨立─┘
塊6stage 驗證)◄── 拿到 DB 連線資訊 + 想驗的塊完成
```
- **塊 0 是所有 Postgres 塊的硬前置**,必須先做。
- **塊 4Redis可與塊 0 平行**(依賴獨立)—— 單人全職時意義不大,列為「若有第二人可平行」。
- **塊 1/2/3 在塊 0 完成後可任意順序**,使用者最關心 model → 建議塊 1 優先。
- **塊 5 需塊 13 的 store 都在**才能做跨 store 交易。
- **塊 6 卡 DB 連線資訊**(唯一硬卡外部)。
### 7.2 最小範圍(塊 0+1單人全職時程
| 階段 | 子任務 | 工作天×6.5hr/day |
|------|--------|---------------------|
| 第 1 階段(拿連線資訊前可做) | 塊0 全部 🟢2036 hrs+ 塊1 全部 🟢2236 hrstestcontainers 全綠) | **6.511 天** |
| 第 2 階段(拿到連線資訊後) | 塊6 精簡6.16.4916 hrs+ 塊0 真 DB 確認 | **1.52.5 天** |
| **最小範圍合計** | | **約 813.5 個工作天** |
> 第 1 階段6.511 天的活)**完全不卡 DB**——只要拍板做最小範圍backend 可立刻開工做到 testcontainers 全綠,等他人把 130 的 DB 開好、給連線資訊,再花 1.52.5 天接上 stage 驗證收尾。**「等 DB」不會 block 大部分工作。**
### 7.3 持久資料範圍單人全職時程
- 第 1 階段(拿連線資訊前):塊 0+1+2+3 全部 🟢 ≈ 91153 hrs ≈ **1424 天**
- 第 2 階段(拿到後):塊 6 完整(含回歸 6.5)≈ 1325 hrs ≈ **24 天**
- 合計 **約 1628 個工作天**
---
## 8. 範圍外(本估算未計入)
要做再另加,不含在上述任何範圍:
| 項目 | 估算 | 說明 |
|------|------|------|
| cluster / converter_job 兩張表 | 未估 | 目前 `main.go` 無對應 in-memory store 被 wireconverter 是 stub、cluster 只有 types |
| remote-proxy `/internal/session-token` 驗證 endpoint | +47 hrs | 目前 remote-proxy 只驗 token 格式不查 store |
| tunnel session 多節點 Redis summary | +1326 hrs24 人天) | handle 不可序列化需「Summary 放 Redis、handle 留本地」混合實作 |
| prod 環境另建 DB | 未估 | prod 是否用 130 或另一套RDS/Cloud SQL/自建)未定,影響韌性/HA/成本 |
---
## 9. 待提供 / 待決策清單
### 9.1 待他人提供的 DB 連線資訊(開好後請給)
| 項目 | 為什麼需要 | 阻塞 |
|------|-----------|------|
| Postgreshost / port / user / password / dbname | 塊 6 連線config 可先用 placeholder 寫好) | 塊 6 |
| **PG 版本** | 影響 `gen_random_uuid()`(需 PG13+ 或 pgcrypto、CITEXT extension → 影響 migration 寫法 | 塊 6.2 |
| sslmodedisable/require/verify-full | DSN 組裝 | 塊 6.1 |
| visionA database 是否已建好、app role 權限 | 確認 migration 能跑、有無建 extension 權限 | 塊 6.2 |
| **Redis有無開、host/port、有無密碼** | 決定塊 4 是否本期做;若不做可叫他人先不開 Redis | 塊 4、塊 6 |
### 9.2 待使用者決策(影響估算/設計)
| # | 項目 | 影響 |
|---|------|------|
| 1 | **範圍**:最小 / 持久資料 / 完整session→Redis 本期做嗎? | 決定總工時與要不要請他人開 Redis |
| 2 | session token 與 pairing token 是否共表by `kind` | 塊 3.4 schema |
| 3 | DB 掛掉降級策略fail-fast(503) 還是 degrade | 塊 5.3 韌性 |
| 4 | 已 soft-delete 的 device serial 能否重註冊? | 塊 2.3 partial unique index |
| 5 | remote-proxy 是否本期要 `/internal/session-token` 驗證 endpoint | 塊 3 加註(+47 hrs |
---
## 10. 給 Orchestrator 的後續建議database.md 更新,本文件未動)
> 以下為 `database.md`(共享文件)應同步更新的缺口,建議另派 Architect 處理,不在本工時估算內。
1. §2.3/§4 models table 補 `faa_object_key TEXT`nullable—— code 已有ADR-017schema 漏。
2. §4 補 DatabaseConfig / RedisConfig env 規格DSN/pool/sslmode/redis password
3. §5 補 migration 第一份清單(哪些 table 在第一個 migration
4. 可選§2.7 補 tunnel session「handle 不可序列化、單節點維持 in-memory」結論。
5.`database.md` 提及「DB 在 130 現成可用」字眼,需更正為「**DB 由他人在 130 另開 visionA 專用實例並提供連線資訊visionA 端不負責 provision只負責跑 migration 與接上**」(與 §1.3 前提一致)。