架構決策記錄 (ADR)
本頁說明
講什麼:出入金系統中關鍵設計決策的背景、可選方案、最終選擇和影響——幫助理解「為什麼是這樣」 適合誰:產品經理評估需求變更影響時、新人理解系統設計邏輯時 前置閱讀:系統架構與資料流格式:每條決策按「背景 → 可選方案 → 決定 → 後果」結構記錄 預計閱讀:4 分鐘 負責人:出入金產品團隊
核心要點:每條架構決策記錄了「背景→可選方案→最終選擇→後果」——PM 評估需求變更時,先看相關 ADR 理解「為什麼是這樣」,避免重複踩坑。
ADR-001:入金匹配採用五維度匹配引擎
狀態:已採納 · 生效中
背景: 使用者透過銀行轉帳入金到證券帳戶時,系統需要將銀行流水(Flow)與使用者的入金申請(Apply)進行配對。早期方案是純人工匹配,但隨著使用者量增長,人工處理無法擴展。
可選方案:
| 方案 | 優點 | 缺點 |
|---|---|---|
| A. 純人工匹配 | 準確率最高 | 不可擴展,處理時效差 |
| B. 單維度匹配(僅金額) | 簡單 | 誤匹配率高(同金額不同使用者) |
| C. 五維度匹配(金額+幣種+銀行+日期+卡號) | 準確率高,可自動化 | 實現複雜,需維護各銀行容差 |
| D. 銀行端回傳唯一 ID | 最精確 | 大部分銀行不支援(僅 BST 可做到) |
決定:採用方案 C,五維度匹配引擎,輔以兩級容差機制(自動入帳容差 + 輔助匹配容差)。
後果:
- 正面:~95% 的入金可自動完成匹配和入帳,運營只需處理 ~5% 的異常
- 負面:需要為每家銀行單獨維護匹配規則和容差參數(12 家銀行 12 套規則)
- 變更影響:新增銀行時必須實現對應的 Match 類(如
BocMatch.php、HsbcMatch.php),並配置容差參數
程式碼位置:deposit/src/app/Business/Match/ 目錄下各銀行 Match 類
ADR-002:出金採用三步審批工作流(Audit → Confirm → Remittance)
狀態:已採納 · 生效中
背景: 出金涉及真實資金流出,需要嚴格的審批控制。但過於繁瑣的審批會影響使用者體驗。需要在安全和效率之間取得平衡。
可選方案:
| 方案 | 優點 | 缺點 |
|---|---|---|
| A. 單步審批(直接匯款) | 最快 | 安全風險高 |
| B. 兩步審批(審核+匯款) | 簡單 | 通道選擇無法獨立審核 |
| C. 三步審批 + 6 套模板 | 靈活,普通出金跳過 Audit | 實現複雜 |
| D. 全流程人工 | 最安全 | 時效差,無法自動化 |
決定:採用方案 C,三步審批(Audit → Confirm → Remittance),透過 6 套模板控制哪些出金需要哪些步驟。普通出金(default 模板)跳過 Audit 直接進入 Confirm。
後果:
- 正面:普通出金只需 2 步,高風險出金有額外審核層
- 正面:BST 通道滿足 6 個條件時可全自動完成,無需人工
- 負面:6 套模板的維護成本;新增審批步驟需建立 Step 類
- 變更影響:新增審批步驟 → 建立 Step 類實現
IFStep介面 → 更新$stepTemplates
程式碼位置:
- 模板定義:
withdraw/src/app/Business/Task.php→$stepTemplates - 步驟實現:
withdraw/src/app/Business/Tasks/Step/{Audit,Confirm,Remittance}.php
ADR-003:BST 銀證採用「內銀系統一接入 + 銀行適配層」架構
狀態:已採納 · 生效中
背景: 招行、民生、天星三家銀行都支援銀證轉帳(BST),但接入協議完全不同:招行/民生用 SM2 加密 Socket,天星用 HTTPS REST API + Mandate 模型。
可選方案:
| 方案 | 優點 | 缺點 |
|---|---|---|
| A. 每家銀行獨立系統 | 互不影響 | 重複開發,統一排程困難 |
| B. 統一業務層 + 銀行適配層 | 業務邏輯共享,銀行差異封裝 | 適配層設計複雜 |
| C. 統一協議轉換閘道 | 最簡潔 | 協議差異太大,強行統一會丟失特性 |
決定:採用方案 B。入金/出金的業務流程(申請、審批、入帳)在上層統一,銀行通訊差異封裝在獨立的服務中(cmb_stock_trans、ms_stock_bank_transaction、天星 API 呼叫層)。
後果:
- 正面:新增 BST 銀行時只需實現適配層,不改動業務邏輯
- 負面:天星的非同步輪詢模型與招行/民生的即時推送模型差異大,上層需要相容兩種模式
- 變更影響:天星出金需要額外的輪詢任務(
AsbBstTransfer+SyncAsbBstWithdraw),而招行/民生不需要
ADR-004:eDDA/eDDI 並行控制採用 Token Bucket(匯豐)+ 悲觀鎖(恒生)
狀態:已採納 · 生效中
背景: 匯豐和恒生的 eDDI 扣款介面都有並行限制。如果並行請求過多,銀行會拒絕或延遲處理。需要在系統側做速率控制。
可選方案:
| 方案 | 優點 | 缺點 |
|---|---|---|
| A. 全域互斥鎖 | 最簡單 | 吞吐量極低 |
| B. 匯豐:Token Bucket 樂觀鎖 | 高吞吐,平滑限流 | 實現較複雜 |
| C. 恒生:InnoDB 行鎖串列化 | 嚴格有序,滿足交易時段約束 | 吞吐量受限 |
| D. 訊息佇列限流 | 解耦 | 引入額外基礎設施 |
決定:
- 匯豐:Token Bucket 樂觀鎖(
UPDATE ... WHERE tokens >= 1),每秒 4 個 Token,資料庫持久化 - 恒生:InnoDB 行鎖悲觀鎖(
SELECT ... FOR UPDATE),因為恒生有交易時段要求,需要嚴格串列
兩家銀行採用不同方案的核心原因:恒生需要感知交易時段(非交易時段請求進入 new_blank 狀態),這要求嚴格的請求排序;匯豐無此約束,用 Token Bucket 可獲得更高吞吐。
後果:
- 正面:兩套方案各自適配銀行特點,穩定運行
- 負面:兩套並行控制機制增加維護成本
- 變更影響:調整匯豐限流 → 修改 Token Bucket 產生速率;調整恒生 → 修改行鎖粒度
程式碼位置:
- 匯豐:
sba_hsbc_eddi/Token Bucket 實現 - 恒生:
sba_hase_eddi/行鎖實現
ADR-005:出金通道選擇採用規則路由 + 人工兜底
狀態:已採納 · 生效中
背景: 系統支援 12 種出金通道(Method),需要根據使用者銀行卡、幣種、金額等條件自動選擇最優通道。但部分場景無法自動判斷。
可選方案:
| 方案 | 優點 | 缺點 |
|---|---|---|
| A. 全自動路由 | 無需人工 | 無法覆蓋所有邊界情況 |
| B. 自動路由 + method=null 人工兜底 | 覆蓋率高,異常有人工保障 | 運營需要通道知識 |
| C. 全人工選擇 | 最靈活 | 不可擴展 |
決定:採用方案 B。calcMethod() 根據銀行卡類型自動路由,無法判斷時設 method = null,由運營在 Confirm 步驟手動選擇。
後果:
- 正面:~80% 的出金通道可自動確定
- 負面:
method = null是 Confirm 步驟卡住最常見的原因 - 變更影響:新增銀行卡類型時必須更新
calcMethod()路由規則,否則會產生 method=null
程式碼位置:withdraw/src/app/Business/CreatorBase.php → calcMethod()
ADR-006:銀行流水採集採用「一銀行一服務」架構
狀態:已採納 · 生效中
背景: 不同銀行的流水接入協議差異極大:中銀用 B2E XML、匯豐用 MT910 SWIFT、工銀用銀企直聯、EWB 用 CSV 檔案匯入。
可選方案:
| 方案 | 優點 | 缺點 |
|---|---|---|
| A. 統一流水採集服務 | 集中管理 | 協議差異太大,強行統一不現實 |
| B. 一銀行一獨立服務 | 各服務獨立部署、獨立維護、互不影響 | 服務數量多 |
| C. 按協議類型分組 | 折中 | 分組標準不穩定 |
決定:採用方案 B。每家銀行有獨立的流水採集服務:
- 中銀:
bochk_flow_go(Go) - 匯豐:
hsbc_bank_flow_service(Python) - 工銀:
icbc_be_relay(Python) - 廣發:
cgb_fps_service(Go) - 渣打:
scb_service(Go)
所有服務將流水統一寫入 acct_trd_record / {bank}_bank_flow 表,由匹配引擎統一消費。
後果:
- 正面:某家銀行服務故障不影響其他銀行;各服務可用最適合的技術棧
- 負面:服務數量多(5+),監控和維運成本高
- 變更影響:新增銀行 → 新建獨立服務 + 實現 Match 類 + 註冊到匹配引擎
ADR-007:入金沖正執行時同步解綁銀行卡
狀態:已採納 · 生效中(有爭議)
背景: 入金沖正(資金退回)後,該筆入金綁定的銀行卡是否應該自動解綁?
可選方案:
| 方案 | 優點 | 缺點 |
|---|---|---|
| A. 沖正時自動解綁 | 防止同一張卡再次出現問題 | 使用者可能需要用同一張卡重新入金 |
| B. 沖正後保留綁定 | 使用者體驗好 | 風險卡可能繼續使用 |
| C. 按沖正原因決定 | 精細化控制 | 實現複雜 |
決定:採用方案 A——沖正時一律自動解綁。
後果:
- 正面:降低同一張有問題的銀行卡反覆入金的風險
- 負面:chargeback 類沖正後,使用者需要重新綁卡才能用同一張卡入金
- 變更影響:如需改為「不解綁」,移除
deposit/src/app/Business/Reverse.php中的解綁邏輯
如何使用 ADR
產品經理評估需求變更時:
- 先查看相關 ADR,理解現有設計的背景和約束
- 評估新需求是否與現有 ADR 衝突
- 如果需要推翻現有決策,新建 ADR 記錄新決策,並標註舊 ADR 為「已廢棄」
新增 ADR 模板:
ADR 模板
## ADR-XXX:決策標題
**狀態**:提議中 / 已採納 / 已廢棄 / 已替代(被 ADR-YYY 替代)
**背景**:
為什麼需要做這個決策?當時面臨什麼問題?
**可選方案**:
| 方案 | 優點 | 缺點 |
|------|------|------|
**決定**:選擇了哪個方案,以及核心理由。
**後果**:
- 正面影響
- 負面影響
- 變更影響(後續需求變更時需要注意什麼)
**程式碼位置**:涉及的核心程式碼檔案讀完之後
| 我想... | 去看 |
|---|---|
| 理解系統整體架構 | 系統架構與資料流 |
| 深入 SBA 編排層 | SBA 資金編排 |
| 了解入金業務全景 | 入金方式總覽 |
| 了解出金審批流程 | 出金生命週期 |