CR #201 » SCS-004_當事人服務程式設計規格.md
SCS-004 當事人服務(party-service)程式設計規格
| 項目 | 內容 |
|---|---|
| 文件編號 | SCS-004 |
| 版本 |
1.3(CR-046 B2-6:投影讀模型接入可選 instrumentId;V4 遷移、JDBC COALESCE 舊事件相容與 CardStatusView 映射)。前版 1.2(CR-031 §4.1.1:真機驗證通過——§9 補 UT-PTY-008~012 與驗證註記;更正 §7.5 編號(v1.1 誤植為 §7.1);更正本表格式;補種子對齊說明)。前版 1.1(CR-031 §2:新增 §7.5 卡片狀態投影——跨服務讀模型;補上 SAD-002 §3.4 載明卻從未實作之訂閱)。前版 1.0
|
| 狀態 | 已核定(規格先行之第一個示範) |
| 對應需求 |
ISS-2.1(信用卡持卡人、歸戶管理)、POC-10(市區停車) |
| 服務埠號 | 8084 |
| 套件根 | com.beam.cardcore.party |
| 權威 schema | CC_PARTY |
| 關聯文件 |
SDD-001 §5.1、BRD-002、BRD-004 POC-10、GOV-005
|
1. 對應需求
| FR 編號 | 需求名稱 | 來源 |
|---|---|---|
ISS-2.1 |
信用卡持卡人、歸戶管理 | 建議書發卡章節 |
| POC-10 | 市區停車(身分參數設定 → 符合資格名單產出 → 停車 → 資格判斷) | BRD-004 §3.10 |
1.1 上層設計依據(本規格不重複這些圖,僅引用)
| 圖種 | 文件 | 數量(實測) | 本服務相關 |
|---|---|---|---|
| ER-Diagram | SDD-001_領域模型與ER-Diagram |
16 個 erDiagram
|
PARTIES/ACCOUNTS/BLOCK_CODE_HISTORY 之實體關係 |
| DFD | SDD-002_DFD與流程分解 |
10 個 flowchart(L0/L1/L2) | 持卡人資料流(ISS-2.1) |
| Sequence | SDD-003_Sequence-Diagrams |
15 個 sequenceDiagram
|
歸戶/持卡人查詢時序 |
| 狀態機 | SDD-005_狀態機設計 |
10 個 stateDiagram
|
⚠️ 當事人狀態機未定義(見 §3.2、§10) |
| API Contract |
SDD-006 + contracts/openapi/party-service.yaml
|
17 份 | 本服務 4 支端點 |
規範:SCS 為程式/服務層級,設計圖一律引用
SDD-*系列,
不在 SCS 內重複繪製(避免兩套圖不同步)。若需新增圖,應先更新SDD-*,再於 SCS 引用。
這也是「規格先行」與「單一真實來源」的體現。
POC-10 對本服務之要求:須能設定並查詢持卡人之身分參數(貼標 flags):
- 每日/每月優惠停車總次數與總時數
- 達月限次簡訊通知註記
- 限定停車場註記
2. 服務職責與邊界
承作:
- 持卡人(Party)基本資料與身分參數(貼標
flags)之維護與查詢 - 歸戶(Account)基本資料查詢
- 歸戶管制碼(
block_codes)設定
不做(屬他服務):
- 卡片核發與生命週期 →
card-service - 授權決策 →
authorization-service - 額度 →
limit-service - 帳務入帳 →
posting-service - 實際停車交易與優惠計算 → 屬收單/行銷範疇,本服務**僅提供資格參數(flags)供其判斷
3. 領域模型
3.1 聚合與值物件
| 型別 | 職責 |
|---|---|
Party |
持卡人聚合(不可變)。含身分別、姓名、身分證遮罩、生日、flags、狀態 |
PartyType |
身分別列舉(PRIMARY/SUPPLEMENTARY/COMPANY) |
Account |
歸戶聚合(帳號、戶別、狀態) |
PartyFlags |
身分參數貼標(POC-10 之優惠停車參數 |
設計要點:
-
不可變:所有轉換回傳新實例(
CSP-001 §4.2)。 - **個資不落地明碼:身分證/手機/Email 皆以
*_enc(BLOB)儲存,對外僅提供*_masked。
3.2 狀態機:當事人/歸戶狀態
⚠️ SDD-005 未定義當事人狀態機。本服務不自創狀態機(避免無依據設計):
PARTIES.STATUS/ACCOUNTS.STATUS僅攜帶與檢核,不做轉換邏輯。- 若未來需要,應先於
SDD-005增訂狀態機後再實作。此缺口已記錄於 §10。
| 維度 | 欄位 | 維護者 | 用途 |
|---|---|---|---|
| 當事人狀態 | PARTIES.status |
本服務(僅設定,無轉換邏輯) | 顯示/稽核 |
| 歸戶狀態 | ACCOUNTS.status | 本服務(僅設定) | 顯示/稽核 |
3.3 身分參數(flags)— POC-10
FLAGS 為 JSON NOT NULL。POC-10 之參數結構(建議,須與行銷/收單確認):
{
"parking": {
"dailyLimitCount": 2,
"monthlyLimitCount": 20,
"dailyLimitHours": 4,
"monthlyLimitHours": 60,
"smsOnMonthlyLimit": true,
"restrictedLots": ["LOT-A", "LOT-B"]
}
}
設計原則:
-
flags採 JSON 欄位存放(DDL 已定義FLAGS JSON NOT NULL),不額外開表(避免過度設計;參數異動頻率低) -
領域層以
PartyFlagsrecord 承載,由基礎設施層做 JSON ↔ record 轉換(領域層不碰 JSON 序列化框架)
3.4 管制碼(block_codes)
ACCOUNTS.BLOCK_CODE + BLOCK_CODE_HISTORY(歷程表)。
- 管制碼設定時**須寫入歷程(稽核留痕)— 銀行案必要。同一交易內完成。
4. 程式結構
| 套件/類別 | 職責 | 對應需求 |
|---|---|---|
domain/model/Party |
持卡人聚合 | ISS-2.1 |
domain/model/PartyType |
身分別列舉 | ISS-2.1 |
domain/model/PartyFlags |
身分參數貼標 | POC-10 |
domain/model/Account |
歸戶聚合 | ISS-2.1 |
domain/port/PartyRepository |
持卡人持久化埠 | ISS-2.1 |
domain/port/AccountRepository |
歸戶持久化埠 | ISS-2.1 |
application/PartyQueryService |
持卡人查詢 | ISS-2.1 |
application/PartyFlagService |
貼標設定(POC-10 參數) | POC-10 |
application/AccountService |
歸戶查詢 + 管制碼設定(含歷程) | ISS-2.1 |
infrastructure/jdbc/JdbcPartyRepository |
JDBC 實作 | ISS-2.1 |
infrastructure/jdbc/JdbcAccountRepository |
JDBC 實作 | ISS-2.1 |
interfaces/rest/PartyController |
持卡人/貼標端點 | ISS-2.1, POC-10 |
interfaces/rest/AccountController |
歸戶/管制碼端點 | ISS-2.1 |
infrastructure/crypto/PartyPiiCipher |
身分證/手機/Email 加解密(common-security) |
SAD-005 §5 |
5. 資料模型(實測值,2026-09-19)
| 表 | 關鍵欄位 | 實測約束 |
|---|---|---|
parties |
id(RAW,PK)、account_id(RAW,NOT NULL)、party_no、party_type、name、id_no_enc(BLOB)、id_no_masked(NOT NULL)、birth_date、mobile_enc(BLOB)、email_enc(BLOB)、relation_type、flags(JSON,NOT NULL)、status(NOT NULL)、created_at、updated_at、version
|
party_type IN ('PRIMARY','SUPPLEMENTARY','COMPANY');ACCOUNT_ID/PARTY_NO/PARTY_TYPE/NAME/ID_NO_MASKED/FLAGS/STATUS/CREATED_AT/UPDATED_AT/VERSION 皆 NOT NULL(PII 明文欄 id_no_enc/mobile_enc/email_enc 可為 NULL(未收集時)。birth_date、relation_type 可為 NULL。 |
| accounts | id(RAW,PK)、account_no、cif_no、account_type、block_code、statement_cycle、statement_day、status、created_at、updated_at、version | (既有 V1 基線;BLOCK_CODE 可空 |
| block_code_history | id、account_id、block_code、reason、operator_id、created_at | 歷程表(稽核) |
| card_status_caches | id(RAW,PK)、card_no_hash(CHAR(256),UNIQUE)、card_id、instrument_id(RAW,可空)、account_id、card_no_masked、status、block_code、expiry_date、last_sequence_no、created_at、updated_at、version | V3 新增、V4 補載體鍵(CR-031 §2/CR-046 B2-6)。跨服務投影——由 card.card.status-changed.v1 餵養,非本服務之權威資料;舊列/舊事件可為 null |
| consumed_events | id(RAW,PK)、event_id(UNIQUE)、event_type、aggregate_id、sequence_no、created_at | V3 新增。消費冪等表——形狀與其他服務一致(SAD-004 §5.4) |
⚠️
card_no_hash為CHAR(256):綁定參數為VARCHAR2→ 比對一律TRIM(同authorization-service之同名表之註記)。
⚠️ 種子資料見 §9;accounts之歸戶主檔須與card/limit之種子對齊(CR-031 §4.4)。
6. API 設計(依 contracts/openapi/party-service.yaml)
| 端點 | 說明 | 錯誤碼 |
|---|---|---|
GET /api/v1/parties/{partyId} |
查詢持卡人(含 flags) | CC-PTY-1001 |
POST /api/v1/parties/{partyId}/flags |
貼標設定(POC-10 身分參數) | CC-PTY-1001 |
GET /api/v1/accounts/{accountId} |
查詢歸戶 | CC-PTY-1001 |
GET /api/v1/accounts/{accountId}/cards |
查詢該歸戶之卡片狀態清單(跨服務讀模型;CR-031 §2、§7.5) |
CC-PTY-4040(歸戶不存在) |
POST /api/v1/accounts/{accountId}/block-codes |
管制碼設定(寫歷程) | CC-PTY-1001 |
回應要點:
-
持卡人/歸戶查詢回傳僅含遮罩個資(
id_no_masked),不含明文或密文。 -
貼標設定回傳更新後之完整
flags。
7. 關鍵邏輯
7.1 個資處理(SAD-005 §5)**
- 寫入:
id_no_enc/mobile_enc/email_enc以AES-GCM加密(common-security之AesGcmFieldEncryptor)。 - 讀取:僅回傳遮罩欄;**明文不進日誌、不進例外訊息。
7.2 貼標設定(POC-10)
- 採**整包覆寫(flags 整體更新)。理由:參數結構單純、避免部分更新之合併複雜度。
- 須驗證
dailyLimitCount/monthlyLimitCount為非負整數;flags不得為 null(DDL NOT NULL)。
7.3 管制碼設定
- 更新
accounts.block_code+ 新增block_code_history(同一交易)。 - 記錄
operator_id、reason(稽核留痕)。
7.4 交易邊界**
- 貼標更新:單表更新,交易包住
UPDATE parties。 - 管制碼:
UPDATE accounts+INSERT block_code_history同一交易。
7.5 卡片狀態投影(跨服務讀模型)——2026-10-01 新增(CR-031 §2)
⚠️ 編號更正:本節於
SCS-004v1.1 初稿誤植為 §7.1,與既有「§7.1 個資處理」衝突;
v1.2 更正為 §7.5(CR-031 §4.1之引用同步更正)。
緣由:
SAD-002 §3.4明載本服務訂閱card.card.status-changed.v1,
惟從未實作(同V2載明卻無餵養者之型)。本節補上。
★ 為何是投影,而非同步查詢(架構上的唯一解)
cardcore-service-acl.yml 之 card-service 只允許 cc-authorization-service 呼叫
→ 本服務(及任何非授權場景:客服/風控/報表)在架構上無法同步取得卡片狀態。
故事件投影是唯一途徑(CR-031 §2.1)。
與
CR-030 §2 #16(同步為主、事件為輔)之關係:該通則禁止「重做同步已做之寫入」。
本項不存在可重做的同步路徑(ACL 即擋死)→ 屬「做沒人做的事」→ 應做。
訂閱主題:cc.card.card.status-changed(SAD-004 §5.4 命名規則)。
| 項目 | 值 |
|---|---|
| 事件型別 | card.card.status-changed.v1 |
| 分區鍵 |
cardId(信封 aggregateId) |
| 存放地 | CC_PARTY.card_status_caches |
| 業務鍵 |
card_no_hash(UNIQUE;與 authorization 之同名表同一慣例) |
| 更新語意 |
upsert by card_no_hash;last_sequence_no 供亂序防護
|
| 讀取端點 |
GET /api/v1/accounts/{accountId}/cards → CardStatusView[]
|
處理(複用既有消費端之三步,皆於同一交易):
① 冪等(consumed_events.event_id)→ ② 亂序(sequence_no 落後於投影列之 last_sequence_no → 略過、不覆寫較新狀態)
→ ③ upsert + 寫 consumed_events。
⚠️ 未知事件型別一律略過並記 WARN(不拋錯——否則新增事件會毒化消費者)。
讀取語意:
| 情境 | 回應 |
|---|---|
| 歸戶不存在 |
404 CC-PTY-4040(與 GET /accounts/{id} 一致) |
| 歸戶存在但尚無卡片投影 | 200 + 空陣列(非 404——「尚無卡片」是合法狀態) |
⚠️ 與
authorization-service之同名表之關係(誠實揭露):二者同形狀、不同語意——
authorization之投影供授權熱路徑(免同步查卡況,<100ms);本服務之投影供歸戶視圖。
刻意不共用:跨服務共用讀模型=跨庫耦合(違反「一服務一 schema」SAD-003 §1 DP-01)。
兩份各自投影是 CQRS 之標準取捨(以儲存換解耦)。
⚠️ 投影為唯讀且最終一致(
SAD-003 §4.2):卡片狀態之權威仍在card-service;
本服務僅提供歸戶視角之查詢,不得作為寫入來源。
⚠️ 不含 PAN:cardNoMasked為遮罩值(先例見card.card.status-changed.v1)——符合 PCI DSS。
BR-16 補充:投影同步保留事件之instrumentId(交易載體最小識別),回應CardStatusView一併揭露;舊事件/舊列無值時回null。instrumentId是內部識別,不以cardNoMasked作比較鍵。
8. 外部依賴與降級**
| 依賴 | 用途 | 失敗處理 |
|---|---|---|
Oracle CC_PARTY
|
權威儲存 | 服務不可用(無降級 — 當事人資料無本地快取,查無即回 CC-PTY-1001。 |
無 Stand-in 降級:當事人資料非即時交易路徑,授權服務未直接呼叫本服務(授權僅用卡片/額度)。故不設計降級,避免不必要的複雜度。若未來授權需查當事人,再另行評估(含快取一致性)。
9. 測試案例(對應 TRP-001)**
| 編號 | 情境 | 預期 |
|---|---|---|
UT-PTY-001 |
查詢持卡人(存在) | 200,回傳遮罩個資 + flags |
UT-PTY-002 |
查詢持卡人(不存在) | 404(CC-PTY-1001) |
UT-PTY-003 |
貼標設定(POC-10 參數) | 200,flags 更新 |
UT-PTY-004 |
貼標設定(flags 為 null) | 400(CC-PTY-1001) |
UT-PTY-005 |
查詢歸戶 | 200 |
UT-PTY-006 |
管制碼設定 | 200,accounts.block_code 更新 + 歷程新增 1 筆 |
UT-PTY-007 |
管制碼設定(無 operator/reason) | 400(稽核欄位必填) |
UT-PTY-008 |
卡片狀態投影(已知事件型別) | 透傳至埠;序號與完整狀態原樣傳遞(CardStatusProjectionServiceTest) |
UT-PTY-009 |
卡片狀態投影(未知事件型別) |
略過且不拋錯、不落到埠(避免毒化消費者;SAD-004 §5.4) |
UT-PTY-010 |
卡片狀態投影(埠回 false=重複/亂序) |
如實回 false,不吞不轉 |
UT-PTY-011 |
查該歸戶之卡片(歸戶不存在) | 404(CC-PTY-4040) |
UT-PTY-012 |
查該歸戶之卡片(歸戶存在、無投影) | 200、空陣列 |
測試資料(種子):需種入至少 1 個歸戶 + 2 個持卡人(PRIMARY/SUPPLEMENTARY),flags 含 POC-10 參數。種子檔:ops/local/init/oracle/06-dev-party-seed.sql。
⚠️ UT-PTY-011/012 需歸戶主檔與其他服務之種子對齊**——見 CR-031 §4.4(party 種子原僅有 …0010,與 card/limit 之 …0001~…0003 對不上,已補齊)。
真機驗證(2026-10-01):
CR-031 §4.1.1——8 項樣本全數通過(含兩項負樣本:重複eventId、較舊sequenceNo)。
⚠️ 投影列由事件餵養,非種子種入;種子僅提供歸戶主檔。
10. 已知限制(誠實揭露)**
- **當事人/歸戶狀態機未定義(SDD-005 無)。本服務不做狀態轉換邏輯,僅設定/攜帶
status。 - POC-10 完整流程未實作:本服務僅提供「身分參數(flags)設定與查詢」;「符合資格名單產出」與「停車資格判斷」屬行銷/收單範疇,不在本服務。
- 歸戶(accounts)僅查詢與管制碼設定;未含開戶/結清等生命週期(屬其他流程)**
-
flags結構未與行銷/收單確認(本文件為建議結構,須跨服務確認後凍結。 -
監控未接(
CSP-004僅追蹤碼)** - 無 Stand-in 降級(見 §8)**
11. 修訂記錄**
| 版本 | 日期 | 作者 | 說明 |
|---|---|---|---|
| 1.3 | 2026-10-16 | WorkBuddy AI |
CR-046 B2-6(#A-249):新增 instrumentId 至 CardStatusSnapshot/card_status_caches 投影/CardStatusView;新增 V4__card_status_instrument_key.sql(nullable+索引);舊事件相容(INSERT 可空、UPDATE COALESCE 不清除既有值);契約由產生器重生。 |
| 1.2 | 2026-10-01 | WorkBuddy AI |
CR-031 §4.1.1 真機驗證通過:§9 補 UT-PTY-008~012(含未知事件型別略過、亂序/重複略過、404 vs 空陣列)+ 真機驗證註記;★ 更正 §7.5 之編號(v1.1 誤植為 §7.1,與既有「§7.1 個資處理」衝突);★ 更正本表格式(v1.1 之列多一欄,與表頭不符);補 §9 種子與其他服務對齊之說明(CR-031 §4.4) |
| 1.1 | 2026-10-01 | WorkBuddy AI |
CR-031 §2(#A-143):新增卡片狀態投影(跨服務讀模型)——補上 SAD-002 §3.4 載明卻從未實作之訂閱。★ 架構上之唯一解:card-service 之 ACL 只允許 cc-authorization-service → 本服務無法同步取得卡片狀態。新增 V3__card_status_projection.sql/CardStatusProjectionPort/CardStatusProjectionService/CardStatusChangedListener/GET /api/v1/accounts/{accountId}/cards。契約端點 92 → 93(0 錯誤)。⚠️ 本版之章節編號誤植為 §7.1(見 1.2 更正) |
| 1.0 | 2026-09-19 | WorkBuddy AI | 初版:規格先行(實作前完成)。依 GOV-005 與 SCS-000 模板撰寫;資料模型約束為 Oracle 實測值。 |