# ADR-001:visionA-backend 採用單一 Go 專案 / 雙 binary 結構 ## 狀態 Accepted — 2026-04-21 ## 背景 (Context) visionA Cloud 後端需同時提供兩種完全不同性質的服務: 1. **API Server**(對 visionA-frontend) - 面向瀏覽器,提供 REST + WebSocket 介面 - **無狀態**:水平擴展容易,可放在無狀態的容器 / Serverless 執行環境 - 流量特性:一般 request/response,延遲敏感(< 200ms) - 負載模式:可預測,隨 DAU 成長線性增加 2. **Remote Proxy**(對使用者端的 local-tool 或 local agent) - 面向 local agent,提供 WebSocket 長連線 + yamux 多工 - **有狀態**:每個 local agent 開一條 tunnel,session 綁在該 proxy 節點 - 流量特性:長連線(小時~天)、資料量差異大(控制指令 vs MJPEG / 推論串流) - 負載模式:長連線 cost 主要在記憶體和 file descriptor,不是 CPU POC(edge-ai-platform)已經將 `edge-ai-server` 和 `relay-server` 分拆成兩個 binary,但兩者位於不同的 module / 不同的 cmd 路徑,共用程式碼是透過 import 來達成。 ## 決策 (Decision) visionA-backend 採用**一個 Go module、兩個 binary**的結構: ``` visionA-backend/ ├── go.mod # module "visiona-backend" ├── cmd/ │ ├── api-server/main.go # 對前端的 REST + WebSocket API │ └── remote-proxy/main.go # 對 local agent 的 tunnel server └── internal/ # 兩個 binary 共用 ├── api/ ├── session/ ├── relay/ ├── tunnel/ ├── storage/ └── ... ``` - 兩個 binary 使用**同一個 go.mod**,可直接共用 `internal/` 下的套件 - 各自有獨立的 `cmd/xxx/main.go`,選擇要載入哪些模組 - 共享狀態(session metadata)抽象為 `internal/session` 的 interface: - `remote-proxy` 端用 `InMemoryStore`(真正持有 `*yamux.Session`) - `api-server` 端用 `ProxyClientStore`(透過 internal HTTP 查 `remote-proxy`,無本地 state) - **雛形 Phase 0 就採雙 binary + internal HTTP**(2026-04-22 Q1 裁決)—— 不做 all-in-one 單進程版本,因為那會讓 `api-server` 有狀態、無法驗證真正的部署拓撲 - **不引入 Redis**(POC 也沒用過;見 ADR-006) ## 考慮過的替代方案 | 方案 | 優點 | 缺點 | 排除原因 | |------|------|------|---------| | **單 binary(API + Proxy 同一進程)** | 最簡單,session 直接記憶體共用 | 無狀態 API 被迫與有狀態 Proxy 綁死;API 無法獨立水平擴展;擴容成本與 tunnel 連線耦合 | 無法水平擴展 API,Production 不可行 | | **雙 module(兩個獨立 Go 專案)** | 完全隔離 | 共用程式碼(types、protocol、session interface)要拆到第三個 module 或手動複製 | 維護兩倍 go.mod,共用改動成本高,POC 已走過這條路 | | **三 binary(API + Proxy + Worker)** | 關注點更分離 | 雛形階段沒有 worker workload,過度設計 | 雛形用不到 | | **單 binary + feature flag 決定角色** | 部署單一 image,啟動時決定角色 | `-mode=api` vs `-mode=proxy` 會讓 main 變成 if/else 分派;binary 含不必要依賴 | 編譯時分開更乾淨 | | **雙 module + 共用 shared module** | 完全隔離 + 有共用 | 三個 Git repo / module 要同步版本,CI 複雜 | 雛形階段不需要 | ## 後果 (Consequences) ### 正面影響 - **部署獨立**:API Server 可放在無狀態的容器集群(如 ECS Fargate / Cloud Run),Remote Proxy 放在支援長連線的 stateful 環境 - **擴展獨立**:API 隨流量 auto-scale;Proxy 隨 tunnel 連線數 scale,兩者擴容邏輯不同 - **Release 獨立**:API 新功能不需要重啟 Proxy 進程(保持 tunnel 不中斷) - **共用方便**:types、protocol、session interface 直接放 `internal/`,編譯時檢查 - **CI / Docker 簡單**:兩個 Dockerfile,同一個 go.mod,依賴管理單一 ### 負面影響(接受的取捨) - **雛形就有 internal HTTP hop**:`api-server → remote-proxy` 多一次 HTTP 呼叫。localhost ~0.1ms,跨機 LAN ~1-5ms。接受此成本換取「雛形部署拓撲 = Production 部署拓撲」 - **觀測稍複雜**:兩個 binary 的 metrics / logs 要分別收集後 join - **本機開發要開兩個進程**:用 `docker-compose` 或 `Makefile dev` target 同時啟動兩者 ### 風險 - **session 查詢延遲**:雛形 localhost internal HTTP 呼叫(~0.1ms,可忽略);Phase 1 跨機 LAN ~1-5ms/次 - **remote-proxy 是雛形有狀態單點**:`remote-proxy` process 重啟 → 所有 tunnel session 遺失,使用者需重新 pair(見 ADR-006) - **多節點路由複雜度(Phase 1)**:當有 N 個 remote-proxy 節點時,api-server 需要能找到「某個 token 的 tunnel 在哪個 proxy」——`tunnel.md` §5.4 已列出候選方案,屆時新增 ADR - **Phase 1 session metadata 共享機制未定**:**雛形不預先決定** Redis / Consul / Gossip,避免過早最佳化(見 ADR-006) ## 合規性 - [x] 與 Architect 評估確認 - [x] 與 PM 確認對業務的影響(部署形態) - [x] 使用者 Q1 裁決 C:雛形就用雙 binary + internal HTTP(2026-04-22) - [ ] 成本影響已評估(Phase 1 多節點時 session metadata 共享方案定案後再估) ## 相關文件 - Design Doc §2(系統邊界與組件) - Design Doc §4(水平擴展策略) - TDD §1(專案骨架)、§2.3(session 模組)、§7(雛形啟動) - ADR-006(雛形不引入 Redis) - `api/api-internal.md`(internal HTTP API 規格)