Compare commits
3 Commits
53e8ab4ae1
...
22f329cdf3
| Author | SHA1 | Date | |
|---|---|---|---|
| 22f329cdf3 | |||
| 46958200eb | |||
| d41a57097f |
449
docs/autoflow/04-architecture/db-integration-plan.md
Normal file
449
docs/autoflow/04-architecture/db-integration-plan.md
Normal file
@ -0,0 +1,449 @@
|
||||
# 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-hours,1 人天 = 6.5 有效 hrs(見 §2 估算假設)。 |
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary(給主管)
|
||||
|
||||
**一句話結論**:把 visionA-backend 接 DB 的工程已被拆到子任務級並完成保守工時估算,**整體區間 22–37.7 人天(視範圍)**;其中約 **80% 的 Go 端工作不需要等 DB 開好就能開工**,等 DB 連線資訊只卡最後 1.5–4 天的 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 | 所有業務持久資料上 Postgres(session 暫留 in-memory) |
|
||||
| **完整** | 上述 + session 接 Redis + 交易/韌性 | **22 – 37.7** | 143 – 245 | 全部持久化 + Redis session + 交易/韌性 |
|
||||
|
||||
> 區間下限 = 一切順利;上限 = 含 schema 細節踩坑與測試補齊。三種範圍皆已含「DB 基礎建設」與「一次 stage 驗證」。
|
||||
|
||||
> 各塊估算已含接 DB 所需的三層測試:unit、integration(testcontainers 真 DB)、併發/連線失敗等邊界,以及既有 e2e 的回歸驗證——這些是接 DB 的必要工程組成,詳見 §5。
|
||||
|
||||
### 兩個關鍵結論
|
||||
|
||||
1. **DB 由他人開好、Go 端不受影響**:DB 機器與 visionA 專用 database 由他人在 stage host(130)開好並提供連線資訊。visionA 端**不負責 provision DB**,但 Go 端所有工作(連線池、config、migration 撰寫、repository 實作、testcontainers 整合測試)一小時都沒省——這些跟「DB 誰開」無關。整合測試改用 testcontainers(本機/CI 一次性 DB),不依賴 130,更可靠。
|
||||
|
||||
2. **「等 DB」只卡最後收尾**:拿到 DB 連線資訊**前**,最小範圍可做到「testcontainers 全綠」的程度(約 6.5–11 個工作天的活),只差最後 stage 接上驗證(1.5–2.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 host(visionA / MC / FAA / converter / minio 都在上面)。
|
||||
- 130 上已存在的 DB container 都屬於其他服務 / 專案,visionA 一個都不該共用。
|
||||
- 130:5432 port 雖 OPEN 但**不對應任何已知 container**(來源不明),**不採信為 visionA 可用**。
|
||||
- ✅ **已確認**:會有人在 130 上幫 visionA **另外開好專用 DB**(Postgres + 視需要 Redis),開好後提供連線資訊。
|
||||
|
||||
**對工時的影響**:
|
||||
|
||||
| 項目 | visionA 端是否要做 | 說明 |
|
||||
|------|-------------------|------|
|
||||
| Provision DB 機器 / 起 container | **不用**(他人做) | 省約 6–12 hrs |
|
||||
| 建 visionA database / 角色權限 | **不用**(他人做,含在「給連線資訊」裡) | 省約 2–4 hrs |
|
||||
| 連線池 / config / migration 撰寫 | **照樣要做** | 跟 DB 誰開無關 |
|
||||
| Repository 實作 + 整合測試 | **照樣要做** | 改用 testcontainers,不依賴 130 |
|
||||
|
||||
→ 一句話:**省下的是「DB provisioning + database 建置 + 130 維運邊界」(約 10–20 hrs / 1.5–3 人天);Go 端全部照做。** 整合測試改用 testcontainers(本機/CI 一次性 DB),這比連 130 測試更可靠(CI 能跑、隔離乾淨),但 testcontainers 設置成本(約 9–17 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 切換)踩坑與測試補齊 | |
|
||||
| **測試範圍** | 含 integration(testcontainers)+ 邊界 + 回歸 | 接 DB 的必要工程成本,已含於各塊估算,詳見 §5 |
|
||||
|
||||
> **塊 0 第一次做最貴**:連線池、migration 機制、testcontainers、CI Postgres 都從零。完成後塊 1–3 每塊攤平、可偏區間低值。
|
||||
|
||||
---
|
||||
|
||||
## 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 存 Summary(handle 仍留本地)。 |
|
||||
|
||||
### 3.3 為什麼不全放 Postgres / 不全放 Redis
|
||||
|
||||
- **不全放 Postgres**:cookie session 高頻讀寫 + 自然過期,Postgres 會變熱點、還要自己寫 cleanup job;Redis 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`) | migration(users 為 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.roles(users 為 Phase 1 stub);JSONB 在 cluster(範圍外)。因此 delta 重心落在塊 1,其次塊 2/3 的 UUID 與 partial unique。
|
||||
|
||||
### 3.5.2 各功能塊 PG vs MySQL 工時對照
|
||||
|
||||
下表以 §4/§5 各塊的 PG 區間為基準,列出 MySQL 版的額外 delta(hrs,順利–卡關)與成因。delta 已含「對應的 schema 改動 + repository code + 測試補齊」:
|
||||
|
||||
| 塊 | 功能 | PG hrs | MySQL delta | MySQL hrs | delta 主因 |
|
||||
|----|------|--------|-------------|-----------|-----------|
|
||||
| 0 | DB 基礎建設 | 20–36 | **+4–7** | 24–43 | Go driver 換(pgx→`go-sql-driver/mysql` 或 GORM)、migration 工具 MySQL dialect、testcontainers 換 MySQL image + helper 調整 |
|
||||
| 1 | **model 接 DB** | 22–36 | **+5–9** | 27–45 | `classes`/`input_shape` 兩個 array→`JSON` 欄位 + 序列化/反序列化 + round-trip 測試重打;UUID 綁定;partial index 替代(functional index / 查詢調整) |
|
||||
| 2 | device 接 DB | 19–32 | **+3–5** | 22–37 | UUID 綁定;`UNIQUE(owner,serial)`×soft-delete 的 partial unique 替代(MySQL 8 無 partial index);雙狀態欄位 round-trip |
|
||||
| 3 | pairing + session token | 30–49 | **+2–4** | 32–53 | UUID 處理(`BINARY(16)`/`CHAR(36)`);token_hash PK 在 MySQL 的型別/索引處理(hash 切換邏輯本身不變) |
|
||||
| 4 | session 接 Redis | 19–32 | **+0** | 19–32 | Redis 與 DB 選型無關 |
|
||||
| 5 | 一致性/交易/韌性 | 20–35 | **+2–4** | 22–39 | **交易隔離級別預設不同**:PG 預設 Read Committed、MySQL InnoDB 預設 Repeatable Read → 一次性 token MarkUsed、cascade 撤銷等併發行為需重新驗證(含 gap lock 行為差異) |
|
||||
| 6 | stage 部署 + e2e 回歸 | 13–25 | **+1–2** | 14–27 | MySQL 版 migration 跑 + 版本特性確認(JSON 型別、collation、functional index 支援,取代 PG 的 `gen_random_uuid()`/CITEXT 確認) |
|
||||
| | **全部加總** | 143–245 | **+17–31** | **160–276** | |
|
||||
|
||||
> 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 精簡 | 51–88 | 7.8–13.5 | **60–102** | **9.2–15.7** |
|
||||
| **持久資料** | 0 + 1 + 2 + 3 + 6 完整 | 104–178 | 16–27.4 | **119–204** | **18.3–31.4** |
|
||||
| **完整** | 0+1+2+3+4+5+6 | 143–245 | 22–37.7 | **160–276** | **24.6–42.5** |
|
||||
|
||||
> 換算同 §2:1 人天 = 6.5 有效 hrs。最小範圍塊 6 取精簡值(6.1–6.4,含 MySQL +1–2 delta);中間範圍不重複計塊 6。
|
||||
|
||||
### 3.5.4 結論:MySQL 比 PG 多多少、值不值得
|
||||
|
||||
**整體 delta**:MySQL 版相對 PG 版多 **+17–31 hrs(約 +2.6–4.8 人天)**,以各範圍中位推算約 **+11%~13%**:
|
||||
|
||||
- 最小範圍:+9–14 hrs(約 +18%,因基數小、塊 0+1 的 driver/array delta 占比被放大)
|
||||
- 持久資料範圍:+15–26 hrs(約 +14%)
|
||||
- 完整範圍:+17–31 hrs(約 +12%)
|
||||
|
||||
→ 比先前粗估的「+10~20%」更收斂:**範圍越大、delta 百分比越低**(基礎建設與 array 的一次性 delta 被攤平),落在 **+11%~18%** 之間,整體中位約 **+13%**。
|
||||
|
||||
**主要 delta 來源排序**:
|
||||
1. **塊 1(model array→JSON)** — 最大,+5–9 hrs,因 `classes` + `input_shape` 兩個 array 欄位都要改 JSON 序列化。
|
||||
2. **塊 0(driver/工具鏈換)** — +4–7 hrs,一次性。
|
||||
3. **塊 2(UUID + partial unique 替代)** — +3–5 hrs。
|
||||
4. **塊 5(隔離級別行為驗證)** — +2–4 hrs,易被低估但攸關 token 一次性正確性。
|
||||
|
||||
**中立建議(該不該換 MySQL)**:
|
||||
|
||||
- ✅ **若團隊已有 MySQL 維運能力、或公司技術標準就是 MySQL** → 這 +13% 的開發 delta 多半值得,因為能省下 PG 的維運學習成本、監控/備份工具重建、DBA 熟悉度等**長期維運成本**(這部分不在本工時表內,但實務上往往大於一次性的 +2.6–4.8 人天)。
|
||||
- ⚠️ **若無特殊原因(團隊對兩者都不熟、或本來就用 PG)** → 對「這個 schema」而言 **PostgreSQL 更自然**:array(classes/input_shape)、JSONB(cluster params)、partial index(soft-delete unique)三項都是 PG 原生、零 delta,換 MySQL 等於用額外工時把這些特性「降級模擬」回來。
|
||||
- **本文件立場(中立)**:純就「現有 schema 的契合度」與「一次性開發成本」看,PG 略優;但 DB 選型應由**維運能力與組織標準**主導,本節提供的是「換 MySQL 的開發成本帳」,不是選型結論。
|
||||
|
||||
---
|
||||
|
||||
## 4. 各功能塊子任務拆解 + man-hours
|
||||
|
||||
> 標記:🟢 = 拿到 DB 連線資訊前就能做(testcontainers 不依賴 130);🔵 = 必須等 DB 連線資訊 / 真 DB 才能驗。
|
||||
|
||||
### 塊 0:DB 基礎建設(所有 Postgres 塊的硬前置)
|
||||
|
||||
| # | 子任務 | hrs(順利–卡關) | 依賴 | 標記 |
|
||||
|---|--------|----------------|------|------|
|
||||
| 0.1 | 加依賴 `pgx/v5`(pgxpool)+ `golang-migrate/v4`,go.mod/go.sum 整理 | 1–2 | — | 🟢 |
|
||||
| 0.2 | `config.DatabaseConfig`(DSN/host/port/user/password/dbname/sslmode/pool size)+ env 解析 + `Enabled()` 模式 + `.env.example` | 2–3 | 0.1 | 🟢 |
|
||||
| 0.3 | 連線池 `internal/db/pool.go`(pgxpool 建池 + 啟動 ping + graceful shutdown) | 2–4 | 0.1,0.2 | 🟢/🔵 |
|
||||
| 0.4 | migration runner(`migrations/` + 啟動自動 migrate up 或獨立 `cmd/migrate`)+ 第一份骨架 migration | 3–5 | 0.1 | 🟢 |
|
||||
| 0.5 | main.go wire 連線池(依 config 決定是否建池,本塊只接骨架) | 1–2 | 0.3 | 🟢 |
|
||||
| 0.6(測試) | **整合測試基礎建設**:testcontainers-go 設置 + 測試 helper(`setupTestDB(t)` 自動 migrate + truncate)+ fixture/factory builder | 4–7 | 0.1,0.4 | 🟢 |
|
||||
| 0.7(測試) | **CI 接 Postgres**:CI workflow 讓 testcontainers 在 CI 跑(Docker-in-CI)+ migration 跑通 + cache 調校 | 3–6 | 0.6 | 🟢 |
|
||||
| 0.8(測試) | 連線池/migration 本身測試(ping、migrate up/down 冪等、池耗盡、連線失敗 fail-fast) | 2–4 | 0.6 | 🔵 |
|
||||
| 0.9 | 整塊 self-review + 過 Reviewer + 修正 | 2–3 | 全部 | 🟢 |
|
||||
| | **塊 0 小計** | **20–36** | | |
|
||||
|
||||
> 一次性最貴投資。「DB 由他人開好」讓 0.3/0.8 不用煩惱在 130 上裝/起 DB,但 testcontainers(0.6)成本如實算入。
|
||||
|
||||
### 塊 1:model 接 Postgres ← 使用者最關心
|
||||
|
||||
| # | 子任務 | hrs(順利–卡關) | 依賴 | 標記 |
|
||||
|---|--------|----------------|------|------|
|
||||
| 1.1 | `PostgresModelRepository`:Get/List(by owner/chip/source)/Save(upsert ON CONFLICT)/Delete(soft) | 4–6 | 塊0 | 🟢 |
|
||||
| 1.2 | `input_shape INT[]` / `classes TEXT[]` pgx array 映射 + `Source` enum + upsert 保留 CreatedAt 語意 | 2–4 | 1.1 | 🟢 |
|
||||
| 1.3 | migration `create_models`(含 `faa_object_key TEXT` nullable — `database.md` §4 漏此欄;index:owner/chip/source、`deleted_at IS NULL` partial) | 2–3 | 塊0 | 🟢 |
|
||||
| 1.4 | main.go wiring 切換(config 決定 Postgres/in-memory,保留 in-memory 給 local dev)+ `seedDemoData` 改寫進 DB | 2–3 | 1.1,塊0 | 🟢 |
|
||||
| 1.5(測試) | **unit/邏輯**:對齊既有 `inmemory_repository_test.go`(SaveAndGet、NotFound、List 三 filter、soft delete、Save 需 ID)→ Postgres 版重打 | 3–5 | 1.1 | 🟢 |
|
||||
| 1.6(測試) | **integration/真 DB**:array round-trip、upsert 保留 CreatedAt、soft-delete 後 List 不含、List filter SQL 正確性、faa_object_key nullable round-trip | 4–7 | 1.1,0.6 | 🟢 |
|
||||
| 1.7(測試) | **邊界**:空 List、重複 Save、併發 Save 同 ID、連線中斷 Get 行為、context cancel | 3–5 | 1.1,0.6 | 🟢 |
|
||||
| 1.8 | 整塊 self-review + 過 Reviewer + 修正 | 2–3 | 全部 | 🟢 |
|
||||
| | **塊 1 小計** | **22–36** | | |
|
||||
|
||||
> model 欄位最多(array + FAA 欄位 + 3 維 filter),測試子任務(1.5–1.7)合計 10–17 hrs,偏重。
|
||||
|
||||
### 塊 2:device 接 Postgres
|
||||
|
||||
| # | 子任務 | hrs(順利–卡關) | 依賴 | 標記 |
|
||||
|---|--------|----------------|------|------|
|
||||
| 2.1 | `PostgresDeviceRepository`:Get/GetBySerial/List(by owner)/Save(upsert)/Delete(soft) | 3–5 | 塊0 | 🟢 |
|
||||
| 2.2 | migration `create_devices`(雙狀態欄位 remote_status/last_seen_at/last_connected_at + `UNIQUE(owner_user_id, serial_number)`) | 2–3 | 塊0 | 🟢 |
|
||||
| 2.3 | `UNIQUE(owner,serial)` × soft-delete 語意(已刪 serial 能否重註冊 → partial unique index `WHERE deleted_at IS NULL`)+ 決策註記 | 2–4 | 2.2 | 🟢 |
|
||||
| 2.4 | main.go wiring 切換 + `seedDemoData` device | 1–2 | 2.1,塊0 | 🟢 |
|
||||
| 2.5(測試) | **unit/邏輯**:對齊既有 device test(SaveAndGet、GetBySerial 跨 owner 不串、List by owner、soft delete、再刪回 NotFound、保留 CreatedAt) | 3–5 | 2.1 | 🟢 |
|
||||
| 2.6(測試) | **integration/真 DB**:unique 衝突、partial unique 讓已刪 serial 可重註冊、雙狀態欄位 round-trip、upsert 保留 CreatedAt | 4–6 | 2.1,0.6 | 🟢 |
|
||||
| 2.7(測試) | **邊界**:空 List、併發註冊同 serial、連線中斷、context cancel | 2–4 | 2.1,0.6 | 🟢 |
|
||||
| 2.8 | 整塊 self-review + 過 Reviewer + 修正 | 2–3 | 全部 | 🟢 |
|
||||
| | **塊 2 小計** | **19–32** | | |
|
||||
|
||||
### 塊 3:pairing_token + session_token 接 Postgres
|
||||
|
||||
| # | 子任務 | hrs(順利–卡關) | 依賴 | 標記 |
|
||||
|---|--------|----------------|------|------|
|
||||
| 3.1 | `PostgresPairingStore`:Create/Validate/MarkUsed/Revoke/List/CleanupExpired | 4–6 | 塊0 | 🟢 |
|
||||
| 3.2 | `PostgresSessionTokenStore`:Create/Get/Revoke/CleanupExpired(含 parent_token_hash 稽核鏈) | 3–5 | 塊0 | 🟢 |
|
||||
| 3.3 | **關鍵改動**:plaintext→token_hash 當 PK。Validate/Get 先 `HashToken()` 再查;確保所有呼叫端傳 plaintext 進、內部一致 hash | 3–5 | 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) | 2–4 | 塊0 | 🟢 |
|
||||
| 3.5 | main.go wiring 切換(兩個 store)+ `seedDemoData` pairing token | 2–3 | 3.1,3.2,塊0 | 🟢 |
|
||||
| 3.6(測試) | **unit/邏輯(pairing)**:對齊既有 test(CreateAndValidate、unknown token、MarkUsed 一次性+冪等、Revoke、CleanupExpired、List by user、Validate expired) | 3–5 | 3.1 | 🟢 |
|
||||
| 3.7(測試) | **unit/邏輯(session token)**:對齊既有 test(CreateAndGet、NotFound、expired、Revoke 冪等、Revoke NotFound、CleanupExpired、NeverExpires ttl=0) | 3–5 | 3.2 | 🟢 |
|
||||
| 3.8(測試) | **integration/真 DB**:hash 當 PK 查詢正確性、TTL 過期、一次性 used 的 DB 層 race(兩併發 MarkUsed 只一成功)、撤銷稽核欄位、共表 kind 隔離 | 5–8 | 3.1,3.2,0.6 | 🟢 |
|
||||
| 3.9(測試) | **邊界**:併發 Validate 同 token、CleanupExpired 大量資料、連線中斷、context cancel | 2–4 | 0.6 | 🟢 |
|
||||
| 3.10 | 整塊 self-review + 過 Reviewer + 修正 | 3–4 | 全部 | 🟢 |
|
||||
| | **塊 3 小計** | **30–49** | | |
|
||||
|
||||
> Postgres 三塊裡最重:兩個 store + plaintext→hash 邏輯切換(漏一個呼叫端就驗不過)+ 一次性語意的 DB 層併發正確性測試。測試子任務(3.6–3.9)合計 13–22 hrs,反映「token 語意錯會出安全問題」的保守估法。
|
||||
>
|
||||
> **加註(不在本塊預設範圍)**:remote-proxy 目前只驗 token 格式不查 store。若 Phase 1 要新增 `GET /internal/session-token/:token` 給 remote-proxy 拉驗證 → 另 **+4–7 hrs**,不含在塊 3 小計。
|
||||
|
||||
### 塊 4:session 接 Redis(userSession;tunnel 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()` | 2–3 | — | 🟢 |
|
||||
| 4.2 | `internal/db/redis.go` 連線 + 啟動 ping + graceful close | 1–3 | 4.1 | 🟢/🔵 |
|
||||
| 4.3 | `RedisUserSessionStore`:Create/Get/Update/Delete/CleanupExpired,用 Redis TTL(idle + absolute 雙 TTL)取代手動 cleanup goroutine;Extra map JSON 序列化 | 4–6 | 4.1,4.2 | 🟢 |
|
||||
| 4.4 | main.go wiring 切換 + 移除/停用 `runUserSessionCleanup` goroutine(改靠 Redis TTL) | 1–2 | 4.3 | 🟢 |
|
||||
| 4.5(測試) | **unit/邏輯**:對齊既有 usersession test(CreateAndGet、NotFound/EmptyID、Get 回副本、Update 移 LastSeenAt 不動 CreatedAt、Update NotFound/Nil、Delete 冪等、Extra round-trip、context cancel) | 4–6 | 4.3 | 🟢 |
|
||||
| 4.6(測試) | **integration/真 Redis**:TTL 實際過期、idle vs absolute 雙 timeout、序列化 round-trip、key 命名/隔離 | 3–5 | 4.3 | 🟢 |
|
||||
| 4.7(測試) | **邊界 + 併發**:race detector、Redis 連線中斷 store 行為、TTL 邊界 | 2–4 | 4.3 | 🟢 |
|
||||
| 4.8 | 整塊 self-review + 過 Reviewer + 修正 | 2–3 | 全部 | 🟢 |
|
||||
| | **塊 4 小計** | **19–32** | | |
|
||||
|
||||
> tunnel session(`internal/session`)value 是活的 yamux Handle 不可序列化 → 維持 in-memory(單節點),本塊不動。跨節點「Summary 放 Redis、handle 留本地」列範圍外(要的話另估 +13–26 hrs ≈ 2–4 人天)。
|
||||
|
||||
### 塊 5:一致性 / 交易 / 連線韌性 / 健康檢查
|
||||
|
||||
| # | 子任務 | hrs(順利–卡關) | 依賴 | 標記 |
|
||||
|---|--------|----------------|------|------|
|
||||
| 5.1 | `internal/db/tx.go` 交易 helper(pgx tx 包裝 + rollback on error + context) | 2–4 | 塊0 | 🟢 |
|
||||
| 5.2 | 跨 store 交易:建 Device + PairingToken 同 tx、刪 Device cascade 撤銷 token、converter job 完成→建 model transactional upsert | 4–7 | 5.1,塊1-3 | 🟢 |
|
||||
| 5.3 | 連線韌性:pool retry / context timeout / 斷線重連 / fail-fast vs degrade 策略(策略需使用者裁決) | 3–6 | 塊0 | 🟢 |
|
||||
| 5.4 | `/healthz` 擴充 DB/Redis ping + 統一 pgx error → `internal/api/errors.go` 映射 | 2–4 | 塊0 | 🟢 |
|
||||
| 5.5(測試) | **交易測試**:rollback(中途失敗整筆回滾)、cascade 撤銷 token 交易邊界、併發交易 | 4–6 | 5.2,0.6 | 🟢 |
|
||||
| 5.6(測試) | **韌性測試**:連線中斷模擬、timeout、健康檢查回應(DB down 時 /healthz 行為) | 3–5 | 5.3,5.4,0.6 | 🟢 |
|
||||
| 5.7 | 整塊 self-review + 過 Reviewer + 修正 | 2–3 | 全部 | 🟢 |
|
||||
| | **塊 5 小計** | **20–35** | | |
|
||||
|
||||
> 區間寬反映「做到多嚴謹」的彈性:最小只做 /healthz + 基本 tx(取下限),完整韌性策略(retry/degrade/cascade 全做)取上限。
|
||||
|
||||
### 塊 6:stage 部署接 130 + e2e 回歸驗證
|
||||
|
||||
| # | 子任務 | hrs(順利–卡關) | 依賴 | 標記 |
|
||||
|---|--------|----------------|------|------|
|
||||
| 6.1 | 拿到他人開好的 DB 連線資訊後:`.env.stage` 注入 DSN/憑證(走既有 secrets 機制,不進 repo) | 1–2 | DB 連線資訊 | 🔵 |
|
||||
| 6.2 | 在 130 的 visionA database 跑 migration + 確認 PG 版本支援 `gen_random_uuid()`/CITEXT(不支援則調 migration) | 2–4 | 6.1 | 🔵 |
|
||||
| 6.3 | stage 部署設定 / compose / CI 部署步驟接上 DB env | 2–3 | 6.1 | 🔵 |
|
||||
| 6.4(測試) | **e2e 持久化驗證**:登入(OIDC)→配對→上傳 model→列 model→**重啟 backend→資料還在**(核心驗收)+ 空庫/seed 啟動正常 | 3–5 | 6.2,6.3 | 🔵 |
|
||||
| 6.5(測試/回歸) | **回歸測試**:接 DB 後既有 e2e/integration(約 6+ 檔)重跑,修因持久化行為改變而壞的測試 | 4–8 | 6.2 | 🔵/🟢 |
|
||||
| 6.6 | 部署驗證彙報 + 修 stage 特有問題 | 1–3 | 全部 | 🔵 |
|
||||
| | **塊 6 小計** | **13–25** | | |
|
||||
|
||||
> 6.5 回歸測試刻意估寬(4–8 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) | 20–36 | 3.1–5.5 | PG | — |
|
||||
| 1 | **model 接 Postgres(含 FAAObjectKey)** | 22–36 | 3.4–5.5 | PG | 塊0 |
|
||||
| 2 | device 接 Postgres | 19–32 | 2.9–4.9 | PG | 塊0 |
|
||||
| 3 | pairing + session token 接 Postgres | 30–49 | 4.6–7.5 | PG | 塊0 |
|
||||
| 4 | session 接 Redis(userSession) | 19–32 | 2.9–4.9 | Redis | —(與塊0平行) |
|
||||
| 5 | 一致性/交易/韌性/健康檢查 | 20–35 | 3.1–5.4 | PG | 塊1–3 |
|
||||
| 6 | stage 部署接 130 + e2e 回歸驗證 | 13–25 | 2.0–3.8 | PG+Redis | 想驗的塊 |
|
||||
| | **全部加總** | **143–245** | **22–37.7** | | |
|
||||
|
||||
### 5.2 三種範圍累計
|
||||
|
||||
| 範圍 | 包含塊 | 累計 hrs | Man-day | 說明 |
|
||||
|------|--------|---------|---------|------|
|
||||
| **最小可行(只 model)** | 0 + 1 + 6 精簡(只跑 6.1–6.4 約 9–16 hrs) | **51–88** | **7.8–13.5** | 模型庫持久化 + 驗證重啟資料還在。塊 0 是硬前置。 |
|
||||
| **持久資料** | 0 + 1 + 2 + 3 + 6 完整 | **104–178** | **16–27.4** | 業務持久資料上 Postgres。session 仍 in-memory(內測可接受)。 |
|
||||
| **完整** | 0+1+2+3+4+5+6 | **143–245** | **22–37.7** | 全部持久化 + Redis session + 交易/韌性。tunnel 多節點 summary 範圍外(另 +13–26 hrs)。 |
|
||||
|
||||
> 三種範圍皆已含塊 0 與一次塊 6 驗證;中間範圍不重複計塊 6。最小範圍的塊 6 只跑 6.1–6.4(不含完整回歸 6.5),故取精簡值。
|
||||
|
||||
### 5.3 各塊測試子任務 hrs
|
||||
|
||||
| 塊 | 測試子任務 | 測試 hrs | 該塊總 hrs |
|
||||
|----|-----------|---------|-----------|
|
||||
| 0 | 0.6+0.7+0.8 | 9–17 | 20–36 |
|
||||
| 1 | 1.5+1.6+1.7 | 10–17 | 22–36 |
|
||||
| 2 | 2.5+2.6+2.7 | 9–15 | 19–32 |
|
||||
| 3 | 3.6+3.7+3.8+3.9 | 13–22 | 30–49 |
|
||||
| 4 | 4.5+4.6+4.7 | 9–15 | 19–32 |
|
||||
| 5 | 5.5+5.6 | 7–11 | 20–35 |
|
||||
| 6 | 6.4+6.5 | 7–13 | 13–25 |
|
||||
|
||||
> testcontainers/CI 一次性基礎建設(塊 0 的 9–17 hrs)做一次、後續所有塊共用。若日後評估要砍工時,最先可砍各塊「邊界」子任務的卡關上限,但**不建議砍 integration 與回歸**(那是「重啟資料還在」的核心保證)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 可先做(不卡 DB)vs 等 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.5–11 個工作天的活),只差最後 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(基礎建設)──┬──► 塊1(model)──┐
|
||||
├──► 塊2(device)─┤
|
||||
├──► 塊3(token)──┼──► 塊5(交易/韌性,需塊1-3)
|
||||
│
|
||||
塊4(Redis)─────(與塊0平行,獨立)─┘
|
||||
|
||||
塊6(stage 驗證)◄── 拿到 DB 連線資訊 + 想驗的塊完成
|
||||
```
|
||||
|
||||
- **塊 0 是所有 Postgres 塊的硬前置**,必須先做。
|
||||
- **塊 4(Redis)可與塊 0 平行**(依賴獨立)—— 單人全職時意義不大,列為「若有第二人可平行」。
|
||||
- **塊 1/2/3 在塊 0 完成後可任意順序**,使用者最關心 model → 建議塊 1 優先。
|
||||
- **塊 5 需塊 1–3 的 store 都在**才能做跨 store 交易。
|
||||
- **塊 6 卡 DB 連線資訊**(唯一硬卡外部)。
|
||||
|
||||
### 7.2 最小範圍(塊 0+1)單人全職時程
|
||||
|
||||
| 階段 | 子任務 | 工作天(×6.5hr/day) |
|
||||
|------|--------|---------------------|
|
||||
| 第 1 階段(拿連線資訊前可做) | 塊0 全部 🟢(20–36 hrs)+ 塊1 全部 🟢(22–36 hrs,testcontainers 全綠) | **6.5–11 天** |
|
||||
| 第 2 階段(拿到連線資訊後) | 塊6 精簡(6.1–6.4,9–16 hrs)+ 塊0 真 DB 確認 | **1.5–2.5 天** |
|
||||
| **最小範圍合計** | | **約 8–13.5 個工作天** |
|
||||
|
||||
> 第 1 階段(6.5–11 天的活)**完全不卡 DB**——只要拍板做最小範圍,backend 可立刻開工做到 testcontainers 全綠,等他人把 130 的 DB 開好、給連線資訊,再花 1.5–2.5 天接上 stage 驗證收尾。**「等 DB」不會 block 大部分工作。**
|
||||
|
||||
### 7.3 持久資料範圍單人全職時程
|
||||
|
||||
- 第 1 階段(拿連線資訊前):塊 0+1+2+3 全部 🟢 ≈ 91–153 hrs ≈ **14–24 天**。
|
||||
- 第 2 階段(拿到後):塊 6 完整(含回歸 6.5)≈ 13–25 hrs ≈ **2–4 天**。
|
||||
- 合計 **約 16–28 個工作天**。
|
||||
|
||||
---
|
||||
|
||||
## 8. 範圍外(本估算未計入)
|
||||
|
||||
要做再另加,不含在上述任何範圍:
|
||||
|
||||
| 項目 | 估算 | 說明 |
|
||||
|------|------|------|
|
||||
| cluster / converter_job 兩張表 | 未估 | 目前 `main.go` 無對應 in-memory store 被 wire(converter 是 stub、cluster 只有 types) |
|
||||
| remote-proxy `/internal/session-token` 驗證 endpoint | +4–7 hrs | 目前 remote-proxy 只驗 token 格式不查 store |
|
||||
| tunnel session 多節點 Redis summary | +13–26 hrs(2–4 人天) | handle 不可序列化,需「Summary 放 Redis、handle 留本地」混合實作 |
|
||||
| prod 環境另建 DB | 未估 | prod 是否用 130 或另一套(RDS/Cloud SQL/自建)未定,影響韌性/HA/成本 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 待提供 / 待決策清單
|
||||
|
||||
### 9.1 待他人提供的 DB 連線資訊(開好後請給)
|
||||
|
||||
| 項目 | 為什麼需要 | 阻塞 |
|
||||
|------|-----------|------|
|
||||
| Postgres:host / port / user / password / dbname | 塊 6 連線(config 可先用 placeholder 寫好) | 塊 6 |
|
||||
| **PG 版本** | 影響 `gen_random_uuid()`(需 PG13+ 或 pgcrypto)、CITEXT extension → 影響 migration 寫法 | 塊 6.2 |
|
||||
| sslmode(disable/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 加註(+4–7 hrs) |
|
||||
|
||||
---
|
||||
|
||||
## 10. 給 Orchestrator 的後續建議(database.md 更新,本文件未動)
|
||||
|
||||
> 以下為 `database.md`(共享文件)應同步更新的缺口,建議另派 Architect 處理,不在本工時估算內。
|
||||
|
||||
1. §2.3/§4 models table 補 `faa_object_key TEXT`(nullable)—— code 已有(ADR-017),schema 漏。
|
||||
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 前提一致)。
|
||||
Loading…
x
Reference in New Issue
Block a user