CR #230 » CR-029_事件流W-2批次C消費端盤點與讀模型設計建議.md
CR-029 事件流(W-2)批次 C:消費端盤點與讀模型設計建議
| 項目 | 內容 |
|---|---|
| 文件編號 | CR-029 |
| 版本 | 1.2 |
| 提出日 | 2026-10-01 |
| 最後修訂 | 2026-10-02(1.2:C-5 全數結案——新增 §7.4 逐條結案表 + 「幽靈待辦」根因揭露(詳 CR-033)。1.1:§5 C-5 首項由 CR-031 §2 落地、C-7 由 CR-031 §3 裁定暫緩) |
| 提出者 | WorkBuddy AI(開發團隊) |
| 緣起 | BEAM PMO 指示「繼續批次 C(消費端),一樣擬定計劃、規格先行、任務追蹤清單缺一不可」。CR-027 §6 早已載明批次 C「需先立讀模型規格」(CSP-001 §5 條件 9:無 SCS 不寫程式)——本 CR 即該「先立規格」之前置盤點。 |
| 範圍 |
SAD-004 §5.2(權威事件清單)+ SAD-002 §3(各服務「事件(訂閱)」)所列、尚未實作之跨服務消費邊 |
| ⛔ 明確排除 |
SAD-003 §4.1 之 7 個 q_* 視圖——全部是「自投影」(見 §3),不是批次 C 之內容;屬 CR-027 §5 #6
|
| 影響範圍 |
SAD-002 §3、SAD-004 §5.2、SDD-006 §4.2、各服務 SCS-00x;GOV-006 §4.8
|
| 狀態 | 盤點完成;C-1/C-2 已完成;C-5 已全數結案(CR-033——實作 1/不做 7/待實作 0);C-7 已裁定暫緩(CR-031 §3);其餘批次依 CR-030 §4 之裁定處理 |
1. 結論摘要(先講重點)
-
批次 C 之真正障礙不是技術,是「目標讀模型/行為尚未被規格定義」——
實測 6 個服務之 schema,除既有 5 條已完成者外,沒有任何一條消費邊有現成的目標表(見 §4)。 -
僅 2 條「語意來源明確 + payload 足夠 + 無冗餘同步路徑」→ 可立即動工(
C-1/C-2)。
★ 其語意來源是既有種子模板(PAYMENT_RECEIVED/STATEMENT_ISSUED),不是我發明的。 -
兩條都撞上同一個契約缺口:模板需要
currency,而兩個事件的 payload 都沒有——
此即CR-027 §5 #10之同族問題(#10為payment.posted.v1、#14為limit.hold.created.v1)。
→ 本 CR 依SAD-004 §5.3(新增可選欄位不升版)補為可選,並登記待追認(比照#14之先例)。 -
另有 8 條屬「與既有同步呼叫重複」(§6)——若事件與同步並存,將重複套用(重複累點/重複佔用)。
此為架構性問題,必須先裁定「事件是唯一路徑,抑或同步為主、事件僅供投影/通知」。 - 批次 C 不可「照單全做」:盲目實作會產出語意錯誤或重複套用的消費者——比不實作更危險。
2. 現況:已完成之消費邊(5 條,不在本批範圍)
| # | 事件 | 消費者 | 效果 | 依據 |
|---|---|---|---|---|
| 1 | card.card.status-changed.v1 |
authorization |
card_status_caches 投影 |
#A-117/#A-118
|
| 2 | limit.limit.adjusted.v1 |
authorization |
account_limit_caches(last_sequence_no) |
#A-119 |
| 3 |
limit.hold.created.v1/.released.v1
|
authorization |
limit_holds 投影(以 authorizationId 去重) |
#A-128 |
| 4 | payment.payment.posted.v1 |
limit |
恢復 account_limits.used_amount
|
#A-124 |
| 5 | authorization.authorization.approved.v1 |
notification |
建立 AUTH_APPROVED 通知 |
#A-125 |
5 條皆有共同形狀:
consumed_events(冪等)+ 目標投影表/服務呼叫 + 亂序防護(SAD-004 §5.4)。
本 CR 之C-1/C-2即複用此形狀(見 §7)。
3. 為何 SAD-003 §4.1 之 q_* 不在批次 C 之範圍
SAD-003 §4.1 列 7 個查詢視圖。逐列比對「來源事件」與「存放服務」:
| 視圖 | 服務(存放地) | 來源事件 | 來源服務 | 是否同一服務 |
|---|---|---|---|---|
q_account_balance |
posting | posting.balance.changed.v1 |
posting | ✅ 自投影 |
q_available_limit |
limit |
limit.limit.adjusted.v1、limit.hold.*
|
limit | ✅ 自投影 |
q_transaction_history |
posting | posting.transaction.posted.v1 |
posting | ✅ 自投影 |
q_authorization_history |
authorization | authorization.* |
authorization | ✅ 自投影 |
q_points_balance |
rewards | rewards.points.* |
rewards | ✅ 自投影 |
q_statement_summary |
statement | statement.statement.issued.v1 |
statement | ✅ 自投影 |
q_merchant_settlement |
merchant | merchant.settlement.submitted.v1 |
merchant | ✅ 自投影 |
7/7 全部是自投影(寫模型 → 事件 → 讀模型,同一服務內)。
這正是
CR-027 §5 #6所質疑者:自投影與該服務之權威表同名同義
(q_transaction_historyvsposting.transactions、q_available_limitvslimit.account_limits…),
實作價值低且易與權威表混淆。
→ 本 CR 不處理q_*;其去留依#6裁定(建議:改寫為跨服務投影,或廢除並以「各服務 CQRS 讀模型清單」取代)。
⚠️ 若#6裁定為「自投影要做」,則批次 C 之範圍須重新界定——請 BEAM PMO 留意此連動。
4. ★ 待實作之消費邊逐條判定(22 條,SAD-004 §5.2)
判定欄位說明:
-
語意來源:該「消費後做什麼」有無文件或既有資料之依據(
—= 未載明) -
目標模型:消費者 schema 中是否已有承接之表(實測
user_tables) - payload:事件 payload 是否足以完成該行為
- 同步冗餘:該行為是否已由既有同步呼叫完成(並存即重複套用)
-
判定:
可做/需裁定/待生產端
| # | 事件 | 消費者 | 用途(SAD-004 §5.2) |
語意來源 | 目標模型 | payload | 同步冗餘 | 判定 |
|---|---|---|---|---|---|---|---|---|
| 1 | authorization.authorization.approved.v1 |
posting | 佔用額度、產生待請款 | — | ❌ 無「待請款」表 | ✅ | ⚠️ auth 已同步呼叫 limit 佔用 | 需裁定 |
| 2 | authorization.authorization.approved.v1 |
limit | 佔用額度 | — | ✅ account_limits
|
✅ | ⚠️ auth 已同步呼叫 limit 佔用 → 重複 | 需裁定(§6) |
| 3 | card.card.status-changed.v1 |
party | 卡片狀態同步 |
SAD-002 §3.4 載訂閱 |
❌ CC_PARTY 無卡片相關表 |
✅ | — | 需先立讀模型規格 |
| 4 | product.parameter.changed.v1 |
全體(16) | 參數熱更新 |
SAD-002 §3.5「AP-04 參數驅動」 |
❌ 各服務無參數快取表 | ⚠️ 無 value(僅知「哪個參數變了」,須回查 product) |
⚠️ 各服務目前同步呼叫 product 取參數 | 需裁定(範圍大) |
| 5 | posting.transaction.posted.v1 |
statement | 帳務事件 | — | ❌ 無 | ✅ | ⚠️ statement 結帳時已同步查 posting | 需裁定(§6) |
| 6 | posting.transaction.posted.v1 |
rewards | 消費累點 |
RWD-5.1(累積規則已定義) |
❌ 無「待累點」表 | ❌ 缺 mcc/merchantId → 累點規則無法判定 |
— | 需裁定(payload) |
| 7 | posting.transaction.posted.v1 |
clearing | 帳務事件 | — | ❌ 無 | ✅ | — | 需裁定(語意) |
| 8 | posting.transaction.posted.v1 |
limit | 釋放佔用 |
SAD-002 §3.2 載「釋放佔用」 |
✅ limit_holds
|
❌ 缺 authorizationId/holdId → 無法對映佔用
|
— | 需裁定(payload) |
| 9 | posting.transaction.reversed.v1 |
statement | 沖正事件 | — | ❌ 無 | ✅ | 同 #5 | 需裁定 |
| 10 | posting.transaction.reversed.v1 |
rewards | 沖回點數 |
RWD-5.1 退款沖回 |
❌ 無 | ⚠️ 有 reversalOf,惟需本地保存「消費→分錄」對照 |
— | 需裁定(payload/模型) |
| 11 | payment.payment.posted.v1 |
posting | 繳款入帳 | — | ✅ transactions
|
✅ | ⚠️ payment 已同步呼叫 posting 入帳 → 重複 | 需裁定(§6) |
| 12 | payment.payment.posted.v1 |
notification | 繳款通知 | ★ 既有種子模板 PAYMENT_RECEIVED(EMAIL/SMS) |
✅ notifications
|
⚠️ 缺 currency → 本 CR 補為可選
|
— | ✅ 可做(C-1) |
| 13 | statement.statement.issued.v1 |
payment | 帳單出帳 | — | ⚠️ 有 AUTO_DEBIT_ENROLLMENTS,惟「帳單成立後做什麼」未載明
|
✅ | — | 需裁定(語意) |
| 14 | statement.statement.issued.v1 |
notification | 帳單通知 | ★ 既有種子模板 STATEMENT_ISSUED(EMAIL) |
✅ notifications
|
⚠️ 缺 currency → 本 CR 補為可選
|
— | ✅ 可做(C-2) |
| 15 | installment.plan.created.v1 |
limit | 分期佔用 | — | ✅ account_limits
|
✅ | — | 待生產端(事件未實作) |
| 16 | installment.plan.created.v1 |
posting | 分期佔用 | — | ✅ transactions
|
✅ | — | 待生產端 |
| 17 | rewards.points.accrued.v1 |
notification | 點數入帳 | ❌ 無對應模板(種子僅 3 種) | ✅ notifications
|
✅ | — | 需裁定(模板) |
| 18 | rewards.points.redeemed.v1 |
posting | 點數兌換 | — | ✅ transactions
|
⚠️ 有 pointsUsed/amountPaid,惟「產生何種分錄」未載明 |
— | 需裁定(語意) |
| 19 | rewards.points.redeemed.v1 |
notification | 點數兌換 | ❌ 無對應模板 | ✅ notifications
|
✅ | — | 需裁定(模板) |
| 20 | clearing.batch.matched.v1 |
posting | 清算比對完成 | — | ❌ 無 | ✅ | — | 需裁定(語意) |
| 21 | dispute.case.opened.v1 |
posting | 爭議受理 | — | ❌ 無 | ✅ | — | 需裁定(語意) |
| 22 | dispute.case.opened.v1 |
notification | 爭議受理 | ❌ 無對應模板 | ✅ notifications
|
✅ | — | 需裁定(模板) |
4.1 SAD-002 §3 獨有之消費邊(不在 SAD-004 §5.2)
| # | 事件 | 消費者 | 依據 | 判定 |
|---|---|---|---|---|
| 23 | party.account.created.v1 |
card |
SAD-002 §3.3「事件(訂閱)」 |
待生產端(payload ⬜ 未定義、未實作) |
| 24 | notification.sent.v1 |
party | SAD-002 §3.15 |
需裁定(SAD-004 §5.2 未列;用途未載明) |
| 25 | merchant.settlement.submitted.v1 |
clearing |
SAD-002 §3.13 之「主要相依」暗示 |
需裁定(無明確「訂閱」記載) |
⚠️ #23~#25 之存在本身即
CR-027 §5 #5(兩份清單不一致)之具體案例。
#5 未裁定前,不宜動工——否則實作範圍將隨清單認定而變。
5. 判定分類與分批計畫
| 批次 | 內容 | 條數 | 前置條件 |
|---|---|---|---|
| C-1 |
payment.payment.posted.v1 → notification(PAYMENT_RECEIVED) |
1 | 事件補可選 currency(本 CR 已執行) |
| C-2 |
statement.statement.issued.v1 → notification(STATEMENT_ISSUED) |
1 | 同上(本 CR 已執行) |
| C-3 |
payload 缺口:#6(缺 mcc/merchantId)、#8(缺 authorizationId/holdId)、#10(缺對照) |
3 | 需裁定「補欄位」或「改契約」 |
| C-4 | 冗餘同步路徑:#2/#5/#11(+#1 部分) | 3~4 | 架構裁定(見 §6) |
| C-5 | 語意/讀模型未定義:#1/#3/#7/#9/#13/#18/#20/#21 | 8 | ✅ 已全數結案(CR-033)——實作 1 條(#3=#A-143)、裁定不做 7 條、待實作 0 條。★ 根因揭露:本批 8 條係依 CR-030 之前之 SAD-004 §5.2 盤點;CR-030 §2 #5 重寫權威清單時已移除/改判這些消費邊,惟未回寫本 CR → 形成幽靈待辦(見 CR-033 §3.1) |
| C-6 | 無對應模板:#17/#19/#22 | 3 | 需裁定「新增模板」或「不做」 |
| C-7 | 範圍大:#4(參數熱更新,16 服務) | 1 | 需先立規格 + 裁定範圍——⏸ CR-031 §3 裁定:設計先行、暫緩實作(實測 9 服務皆無參數快取 → 無快取可失效;且 SAD-004 §5.2 之「全體」實為可呼叫 product 之 9 服務,已更正) |
| C-8 | 待生產端:#15/#16/#23 | 3 | 生產端未實作(屬批次 A-4 之「payload 未定義」群) |
| C-9 | 清單權威未定:#24/#25 | 2 | CR-027 §5 #5 |
6. ★ 架構性問題:事件 vs 同步呼叫之重複(本 CR 最重要之發現)
實測發現 3 條消費邊所描述的行為,已由既有的同步呼叫完成:
| 事件 | 消費者 | 文件所述用途 | 既有同步路徑 | 並存之後果 |
|---|---|---|---|---|
authorization.authorization.approved.v1 |
limit | 佔用額度 |
AuthorizeUseCase 呼叫 limit-service 同步佔用
|
重複佔用(額度被扣兩次) |
payment.payment.posted.v1 |
posting | 繳款入帳 |
payment-service 呼叫 posting-service 同步入帳
|
重複入帳(帳務重複) |
posting.transaction.posted.v1 |
statement | 帳務事件 |
statement 結帳時同步查詢 posting
|
若事件另建投影,兩套真相 |
⚠️ 這不是「實作細節」,而是架構決策:
SAD-004 §5.2之「主要消費者」欄係事件驅動願景之描述,惟現行實作已採同步呼叫
(SAD-002 §3.x之「同步呼叫」欄即載明)。兩者並存必然重複套用。三種可能方向(需 BEAM PMO 裁定):
(a) 事件為唯一路徑——移除同步呼叫,改由事件最終一致(改動大,且授權路徑需即時佔用,不建議);
(b) 同步為主、事件為輔——事件僅用於投影/通知(不重做同步已做之寫入)——
本 CR 之C-1/C-2即採此;SAD-004 §5.2之「用途」欄須改寫以排除「重做寫入」之語意(建議);
(c) 逐案裁定——由 BEAM PMO 就 3 條各自定方向(最保守)。建議採 (b):理由——① 授權/繳款之即時性要求使同步不可去;
② 事件驅動之價值在跨服務投影與通知,重做寫入並無增益且引入重複風險;
③(b)下C-1/C-2已可動工,且#1/#5/#7/#9/#20/#21 之「用途未載明」問題
亦可一併以「事件不重做寫入」之原則消解(僅餘「投影什麼」之問題)。
7. C-1/C-2 之執行(本 CR 已動工)
7.1 語意來源(非臆造)
| 事件 | 模板(既有種子,V2__notification_templates_and_notifications.sql) |
通道 | 所需變數 |
|---|---|---|---|
payment.payment.posted.v1 |
PAYMENT_RECEIVED |
SMS/EMAIL(皆 enabled=1) |
amount、currency
|
statement.statement.issued.v1 |
STATEMENT_ISSUED |
EMAIL(enabled=1) |
statementMonth、totalAmount、currency、paymentDueDate
|
模板為既有種子資料——本 CR 僅「把事件接到既有模板」,未新增任何模板(新增模板者列
C-6待裁定)。
7.2 契約缺口之處理:補可選 currency
| 事件 | 現行 payload(SDD-006 §4.2) |
模板需要 | 缺口 |
|---|---|---|---|
payment.payment.posted.v1 |
paymentId/accountId/amount/allocations[]/availableLimitAfter
|
amount、currency
|
缺 currency |
statement.statement.issued.v1 |
statementId/accountId/statementMonth/totalAmount/minimumDue/paymentDueDate
|
statementMonth、totalAmount、currency、paymentDueDate
|
缺 currency |
處置:依 SAD-004 §5.3「新增可選欄位 → 不升版」補為可選。
先例(本專案已有兩次同樣處置,皆已執行):
-
CR-027 §5 #11:approved.v1擴充cardNoMasked/merchantId(可選)——#A-125執行 -
CR-027 §5 #14:limit.hold.created.v1補currency(可選)——#A-128執行
⚠️ 與
#10之關係:#10即「payment.payment.posted.v1缺currency」,建議 (b) 補為可選。
本 CR 依#10之建議選項 (b) 執行(因 C-1 需要),並登記待 BEAM PMO 追認——
與#14之處理完全一致(該項亦為「依建議執行 + 登記待裁定」)。
statement.statement.issued.v1為同族第三例,建議一併納入#10之裁定範圍。
7.3 實作與驗證
| 項 | 內容 |
|---|---|
| 規格先行 |
SCS-008 §7.2(payment 補可選 currency)/SCS-011 §7.2(statement 補可選 currency)/SCS-016 §7.11/§7.12(兩消費端) |
| 生產端 |
PaymentEventFactory 補 currency(Payment.currency() 本即存在);StatementEventFactory 補 currency(Statement.currency() 本即存在)——皆非新資料
|
| 消費端 |
PaymentPostedNotificationService + StatementIssuedNotificationService + 對應 Listener(複用 #A-125 之形狀:冪等 → 亂序 → submit → markConsumed) |
| 共用化 |
AuthApprovedNotificationPort → EventConsumptionPort(consumed_events 表本即通用,僅名稱綁 auth)——避免複製三份 |
| 真機驗證 | 觸發繳款/出帳 → 事件 → 通知建立;負樣本:同 eventId 重放不重複、sequenceNo 落後略過 |
⚠️ 語意僅為「通知被正確建立」——遞送通道仍為真阻塞(
SCS-016 §8:唯一實作為本地 Log stand-in)。
與#A-125同一立場:不宣稱「客戶已收到」。
7.4 ★ C-5 之結案(CR-033,2026-10-02)
BEAM PMO 指示「先執行
C-5其餘 8 條目」 → 逐條查證後結案,詳見CR-033。
| # | 事件 → 消費者 | 結案 |
|---|---|---|
| 1 |
authorization.approved.v1 → posting |
不做(CR-030 §4.1:同步已做) |
| 3 |
card.status-changed.v1 → party
|
✅ 已完成(#A-143/CR-031 §2) |
| 7 |
posting.posted.v1 → clearing |
不做(CR-030 §4.1:批次對帳) |
| 9 |
posting.reversed.v1 → statement |
不做(CR-030 §4.1:結帳同步查詢) |
| 13 |
statement.issued.v1 → payment |
不做(非事件消費邊;「自扣」屬未實作功能) |
| 18 |
rewards.points.redeemed.v1 → posting |
不做(SDD-003 §13 為同步呼叫,#16 不得重做) |
| 20 |
clearing.batch.matched.v1 → posting |
不做(§5.2 已標「不做消費端」;阻塞為會計科目設計) |
| 21 |
dispute.case.opened.v1 → posting |
不做(非事件消費邊;帳務調整屬結案路徑) |
→ 實作 1 條、裁定不做 7 條、待實作 0 條。
★★ 根因:本批係「幽靈待辦」
C-5 之 8 條係依 CR-030 之前之 SAD-004 §5.2 盤點而得;
CR-030 §2 #5 將該清單重寫為唯一權威時已移除/改判這些消費邊,
惟未回寫本 CR → 本批仍顯示為待辦(詳 CR-033 §3.1,含 git show 之新舊清單對照)。
教訓:重寫權威清單後,必須回寫「以舊清單為基礎之盤點」——否則產生幽靈待辦。
8. 請求裁定事項(給 BEAM PMO)
-
§6 架構方向:建議採 (b)「同步為主、事件為輔」——事件僅供投影/通知,不重做同步已做之寫入。
此為批次 C 能否大規模推進之前提。 -
§5
C-3payload 缺口:posting.posted.v1是否補authorizationId/mcc/merchantId(可選)?
(#8之「釋放佔用」若採 (b) 且「不重做寫入」,則此條可降級為不做——請一併裁定。) -
§5
C-6模板:rewards.points.*/dispute.case.opened之通知是否要做?
若要做,模板由誰提供(新增種子模板屬「新增業務文案」,不應由開發自行編寫)。 -
CR-027 §5 #10之追認:本 CR 已依建議 (b) 為payment.posted.v1及statement.issued.v1
補可選currency(不升版);請追認,或指示改為必填/升版。 -
CR-027 §5 #5(清單權威)與#6(q_*語意):此二項直接決定批次 C 之範圍
(#5決定「哪些消費邊存在」;#6決定「自投影是否要做」)→ 請優先裁定。
9. 修訂記錄
| 版本 | 日期 | 說明 |
|---|---|---|
| 1.2 | 2026-10-02 |
C-5 全數結案(CR-033)——BEAM PMO 指示「先執行 C-5 其餘 8 條目」。逐條查證後結論:實作 1 條(#3=#A-143)、裁定不做 7 條、待實作 0 條。★★ 揭露根因:本批 8 條係依 CR-030 之前之 SAD-004 §5.2 盤點;CR-030 §2 #5 重寫權威清單時已移除/改判這些消費邊,惟未回寫本 CR → 形成幽靈待辦(已以 git show 9937bd0 取新舊清單對照為證)。新增 §7.4 結案表;§5 之 C-5 列與檔頭狀態同步更新 |
| 1.1 | 2026-10-01 | 依 CR-031(批次 C 剩餘項之裁定)更新 §5:C-5 首項落地(party 卡片狀態投影,SCS-004 §7.5,真機驗證通過)、C-7 裁定暫緩(實測 9 服務皆無參數快取);C-5 其餘條目仍待逐條立讀模型規格 |
| 1.0 | 2026-10-01 | 初版:批次 C(消費端)盤點——SAD-004 §5.2 22 條 + SAD-002 §3 獨有 3 條,逐條判定「語意來源/目標模型/payload/同步冗餘」;排除 q_*(7/7 自投影);★ 揭露架構性問題(事件 vs 同步呼叫重複,3 條);C-1/C-2 動工(語意來源=既有種子模板;依 SAD-004 §5.3 補可選 currency);其餘分為 C-3~C-9 共 9 批待裁定 |