搜尋開發指南

試試「多租戶」、「啟動」或「outbox」。
搜尋僅使用本站文件,不會傳送至外部服務。

文件目錄

NEXUSPRO / HANDBOOK

後端開發規範

先凍結契約,再安排實作;先保留明確的失敗語意,再談韌性與交付。

內容核對 2026-09-28·圖文指南

本章目標與前置條件

把需求拆成契約、儲存、業務、授權與驗證的最小閉環。開工前閱讀目前 checkout 的 AGENTS.md、相關程式碼與現有測試;本指南是閱讀入口,不取代 repository 的完整規範。

確認需求範圍、驗收條件及不做的部分,並檢查工作區既有變更。不能為了完成自己的任務覆蓋別人的未提交內容。

契約先行

新增或修改業務行為的順序:

  1. 先在方案寫清楚端點、權限點、請求與回應、錯誤、破壞性變更,供審核。
  2. 核准後更新 api/openapi.yaml,凍結對外形狀。
  3. 需要儲存變更時,新增 db/migrations/ 的前向 migration。
  4. 更新 db/queries/ 並重新生成 sqlc。
  5. 調整 Repository 介面、Service 與 HTTP adapter。
  6. 補測試與實際行為驗證。

不要為了符合順序而硬加一份空 migration。純讀取新行為若沿用既有資料結構,應明確說明不需要 schema 變更。

契約順序有機械守門:tests/contract/openapi_route_parity_test.go 要求 api/openapi.yaml 與路由登錄雙向一致。已凍結但還沒有路由的 operation,必須登記在同檔的 contractFirstOpenAPIPaths 並標明 milestone,否則步驟 2 到 5 之間 make ci-core 會失敗。新增路由時,要同批更新三份封閉清單:

  • 路由宣告快照 internal/route/testdata/registry.golden;
  • tests/contract/openapi_test.go 的 trackedOpenAPIOperations;
  • tests/contract/operator_surface_test.go 的精確路由集合。

OpenAPI 沒有權限點、risk、page、tenant_wide 等授權中繼資料的結構化欄位,只有少數 description 以文字提及;以 registry.golden 與各 register*Routes 宣告為準。

分層與交易

業務域以 package 分組。禁止子 package 回呼 root service、禁止跨層直連、禁止以多份類型別名維持相同模型。

分層由 tests/contract/import_boundaries_test.go 強制執行:

  • service 不得 import internal/platform;
  • 不同 service domain 之間不得互相 import;
  • internal/repository/postgres 與 internal/wiring 只屬組裝層;
  • Temporal SDK 只能出現在 internal/platform/temporal;
  • cmd/worker 不得依賴 HTTP 層。

違反其中任何一條,make ci-core 的 contract 測試就會失敗。

一個交易不跨域。批次同步預設能隔離單筆可分類錯誤;如果整批必須原子提交,寫出業務理由。resolver 失敗不能偽裝成合法空結果,尤其當下游會刪除、invalidate 或覆寫資料時。

涉及樹、唯一名稱/路徑或來源保護的寫入,要檢查「讀取驗證 → 寫入」之間能否被另一個交易改變。做法是:樹鎖先於祖先讀取,唯一性要有 DB constraint 兜底,並用真 PostgreSQL 的兩條連線加 barrier 測試,不能只測串行。

Migration 與程式生成

新增 migration 預設包含 up/down。確實不可逆的資料修正要標明理由、備份與恢復方式;已發布 migration 不改寫。資料搬移先 dry-run 統計影響,再取得相應授權。migration 還有這幾項機械要求:編號必須連號,新檔要追加到 FROZEN.sha256,新表必須有 COMMENT ON TABLE。細節見Repository 與資料存取。

以下在後端根目錄執行,會更新生成物,應安排在自己擁有的變更範圍內:

bash
make sqlc
make generate

make generate 會經 mapper 依賴執行 sqlc,再執行 go generate ./...。後者也會重寫 .env.example、api/config-reference.md 與 api/error-codes.md。交付時檢查產生的 diff,不手改生成檔來「修」契約漂移。make drift(含在 make ci-core 內)會核對 sqlc、mapper、config、errcodes 這四類生成物是否最新。

新增表預設 ENABLE/FORCE RLS。手寫 normalize/equal/copy 必須覆蓋所有欄位,並由實際經過該路徑的測試守門;不能只測 struct 建構。

失敗語意與可觀測性

空集合只表示合法無資料,不代表解析或查詢失敗。外部 timeout、結構損壞與業務拒絕應可區分,呼叫端才能決定重試、回報或停止。

錯誤模型與請求解碼

HTTP 回應只有兩種信封,定義在 internal/api/v1/response.go:

  • 成功:{"data": …};204 沒有 body。
  • 失敗:{"error": {code, message, reason_code, field_errors, row_errors, trace_id}}。

業務錯誤一律以 errs.New(code, …) 建立已登記的 AppError(internal/errs),未登記的 code 會直接 panic。handler 的 writeError 把任何非 AppError 收斂為 10000 internal_error,只有 5xx 才寫伺服器錯誤日誌。常見分類如下:

情況HTTP 與錯誤碼
依賴暫時不可用503,10500 service_unavailable
未預期的內部失敗500,10000 internal_error
業務拒絕、驗證失敗各 domain 登記的 4xx 碼
請求 body 超過上限413,10413 request_body_too_large
未知欄位或 JSON 損壞400,10002 invalid_json_body
body 含多於一個 JSON 值400,10003 multiple_json_values

後三項來自 readJSON 的嚴格解碼。新增錯誤碼時改 internal/errs/codes.go,再用 make generate 重建 api/error-codes.md,由 errcodes-drift 守門。

日誌、授權與旗標

  • 外部整合收斂在 platform,不將供應商欄位帶入業務模型。
  • 授權使用精確 permission key,不以子字串或前綴猜測。
  • 記錄可追查的 request/trace/業務識別與重試次數;不要在日誌公開 token 或個資。
  • Feature flag 關閉時應維持已交付的 baseline;共享 helper 的新分支也要檢查。

時間與跨平台精度

一次業務評估只取一次時間並往下傳,避免不同 batch 跨 deadline 得到互相矛盾的答案。

PostgreSQL timestamptz 的微秒精度與 Go 納秒值不同。需要持久化時間的 package 要在唯一取樣點處理精度;測試主動注入次微秒殘值,不依賴開發機時鐘剛好能通過。

go
// 示例:在持久化時間的統一取樣點收斂精度。
func persistedNow(now func() time.Time) time.Time {
    return now().UTC().Truncate(time.Microsecond)
}

此片段是說明原則的示例,不是要求新增同名函式。先查既有 package 的取樣點,不建立第二份實作。

這條規則由 tests/contract/persisted_clock_test.go 守門。它掃描的是一份人工維護的 package 清單,不會自動涵蓋新 package;新增會持久化時間戳的 package 時,要在同一批變更中把它加進清單。

驗證與交付

驗證層真實命令/入口證據邊界
驗證層真實命令/入口證據邊界
------------------------------------------------------------------------------------------------------------------------------------------
聚焦單元對受影響 package 執行 go test只證明受測路徑
本機基礎門make ci-corehermetic 檢查,不等於外部依賴可用;make unit 會跑 python3 的 approved-config 測試,機器上需要 python3
本機完整門make ci-local包含 required integration/E2E,仍不是部署現場
Apifox 聯調make apifox-isolated;手動時才用 make apifox-test需要 Docker、已登入的 Apifox CLI 與有效場景斷言
部署驗收在指定環境重建並實際打 API/操作頁面僅代表所測版本、租戶與場景
bash
# 後端 repository 的交付命令;有成本與環境前置,不是本站測試
make ci-local
make apifox-isolated

本地交付須依 repository 要求通過兩道門:make ci-local 與 Apifox 場景。

一般情況用 make apifox-isolated。

  • 它為目前工作樹建一套隔離 compose,建立管理員與 self-only 員工,植入確定的假勤資料,再執行雲端套件,結束後只清理本次資源。
  • 需要 Docker、已登入的 Apifox CLI 與現有 .env,說明見 docs/testing/apifox-isolated.md。
  • 腳本會主動 unset NEXUS_DEV_PASSWORD,改由暫存的 0600 fixture JSON 提供身分。

手動執行 make apifox-test 時:

  • 除了登入密碼,還必須用 APIFOX_RUNTIME_VARS 提供 myleave*、attendance* 等場景 fixture 變數,並搭配相符的 APIFOX_ENV。
  • 缺少這些變數時,tools/run_apifox.py 會在送出請求前以 exit 2 拒絕。

Apifox 顯示 success 不夠,還要核對實際斷言數大於零。缺少 CLI 登入、fixture 或雲端寫入授權時,明確回報受阻,不虛構通過。

預期結果: 報告能逐條回答變更檔案、行為是否改變、執行了哪些驗證、失敗與未驗證項目。業務端點場景、當日開發記錄與提交規則以當前 AGENTS.md 為準。push、部署與跨倉修改需獨立授權。

常見錯誤

  1. 單元綠就說完成:缺少聯調與環境行為證據。
  2. 把查詢失敗當空資料:下游可能發生破壞性動作。
  3. 每條讀取路徑各寫一份計算:枚舉與比較精度也會漂移。
  4. 只測錯誤分支:壞引擎也可能正確拒絕;必須驗證成功路徑真的允許且回傳正確結果。

相關文件

前往新增業務功能實戰,用真實的草稿預覽實作對照上述規則。

內容來源與核實範圍

以下路徑相對於所列業務倉庫;核實層級為「已讀原始碼」,不是本輪業務測試或部署驗收。

  • nexus-pro-be-plus/AGENTS.md
  • nexus-pro-be-plus/Makefile
  • nexus-pro-be-plus/api/openapi.yaml
  • nexus-pro-be-plus/internal/service/form/instance/preview.go
NexusPro 開發指南以程式碼為準 · 以驗證為據