Project

General

Profile

CR #201 » SCS-005_產品服務程式設計規格.md

內容更新:內部稱謂去識別化(2026-10-18) - Redmine Admin, 10/18/2026 05:32 AM

 

SCS-005 產品服務(product-service)程式設計規格

項目 內容
文件編號 SCS-005
版本 1.4(CR-045 D4:於 §7.1/§10 #A-130 新增正向參照至 SCS-002 §7.14 首案消費端規格。前版 1.3(★ 修正 paramValue 之 JSON 序列化缺陷——STRING/JSON 型別原產生非法 JSON → ORA-40441 → HTTP 500)。前版 1.2(GOV-006 4.8 W-2 推廣・CR-027 批次 A-4:新增 §7.1 參數變更事件輸出(product.parameter.changed.v1)——SAD-002 §3.5 明列、SDD-006 §4.2 已定義 payload,惟從未有人發射。★ 分區鍵=namespace/paramKey;★ 併補 @Transactional(原方法未標交易)。真機驗證通過。前版 1.1(C-2 落地:5 端點實作+版本化/熱更新語意定案+真機 e2e;2026-09-22)
狀態 已核定(規格先行)
對應需求 COM-6.1(參數設定)、COM-6.2(共同類/產品主檔)
服務埠號 待骨架產生時依 CSP-006 §4 分配(自 8081 起編,參 spec/services.yaml port_start)
套件根 com.beam.cardcore.product
權威 schema CC_PRODUCT
關聯文件 SDD-001 §6、SDD-006 §4、BRD-002、GOV-005

1. 對應需求

FR 編號 需求名稱 來源
COM-6.1 參數設定(參數化,P0–P3) BRD-002
COM-6.2 共同類/產品主檔查詢 BRD-002

產品服務亦承載卡片產品主檔(PRODUCTS)與外部組織碼對照(CHARSET_MAPPINGS),為授權、定價、分期等服務提供產品維度參數來源。

1.1 上層設計依據(本規格不重複這些圖,僅引用)

圖種 文件 數量(實測) 本服務相關
ER-Diagram SDD-001_領域模型與ER-Diagram 16 個 erDiagram PRODUCTS/PARAMETERS/CHARSET_MAPPINGS/RULE_SETS 實體(SDD-001 §6)
DFD SDD-002_DFD與流程分解 10 個 flowchart(L0/L1/L2) 參數設定資料流(COM-6.1)
Sequence SDD-003_Sequence-Diagrams 15 個 sequenceDiagram 產品/參數查詢時序
狀態機 SDD-005_狀態機設計 10 個 stateDiagram ⚠️ PRODUCTS.status 轉換(DRAFT→ACTIVE→SUSPENDED→RETIRED)以 CHECK 約束+應用邏輯控管,未定義為正式狀態機(見 §3、§10)
API Contract SDD-006 + contracts/openapi/product-service.yaml 17 份 本服務 3 支端點

2. 服務職責與邊界

承作:

  • 卡片產品主檔(PRODUCTS:產品代碼、卡名、組織 VISA/MC/…、卡等、票種)
  • 參數化(PARAMETERS:以 JSON 承載各類參數,依 NAMESPACE/PARAM_KEY 生效區間管理)
  • 組織碼對照(CHARSET_MAPPINGS:內部碼↔外部組織碼)
  • 規則集(RULE_SETS:資格/費用/紅利/利息等規則,供他服務引用)

不做(屬他服務):

  • 費率試算與利息計算 → pricing-service
  • 授權決策 → authorization-service
  • 紅利累兌 → rewards-service

3. 領域模型

  • 聚合:Product(產品主檔)、ProductParameter(參數,含生效區間)、CharsetMapping(組織碼對照)、RuleSet(規則)
  • 值物件:ProductCode、OrgCode(受限於 PRODUCTS.org CHECK)、ParamKey、RuleType
  • 狀態:PRODUCTS.status ∈ {DRAFT, ACTIVE, SUSPENDED, RETIRED}(CHECK 約束,預設 DRAFT);轉換由應用層控管,未建模為 SDD-005 狀態機

4. 程式結構

套件/類別 職責 對應需求
domain/model/Product ProductParameter CharsetMapping RuleSet 聚合與值物件、不變式 COM-6.2
domain/port/ProductRepository ParameterRepository RuleRepository 持久化埠 —
application/ProductQueryService 產品主檔查詢 COM-6.2
application/ParameterService 參數設定與生效區間管理 COM-6.1
application/RuleEngine 規則評估(依 PRIORITY) (規則消費側,供他服務)
infrastructure/jdbc/*RepositoryImpl JDBC 實作 —
interfaces/rest/ProductController ParameterController REST 端點 COM-6.1/6.2

5. 資料模型(約束為 Oracle 實測值)

表 關鍵欄位 約束(實測)
PRODUCTS ID RAW(16) PK;PRODUCT_CODE VARCHAR2(16) NOT NULL UNIQUE;CARD_NAME;ORG;CARD_GRADE;TICKET_TYPE;STATUS VARCHAR2(16) DEFAULT 'DRAFT' org IN ('VISA','MASTERCARD','JCB','AMEX','UNIONPAY','DINERS','DISCOVER');status IN ('DRAFT','ACTIVE','SUSPENDED','RETIRED');EFFECTIVE_FROM NOT NULL、EFFECTIVE_TO 可空(生效區間)
PARAMETERS ID PK;NAMESPACE VARCHAR2(40);PARAM_KEY VARCHAR2(80);PARAM_VALUE JSON NOT NULL;VALUE_TYPE;EFFECTIVE_FROM/TO DATE value_type IN ('STRING','NUMBER','BOOLEAN','JSON','AMOUNT','RATE');UQ_PARAM UNIQUE(NAMESPACE,PARAM_KEY,EFFECTIVE_FROM)
CHARSET_MAPPINGS INTERNAL_CHAR;EXTERNAL_CODE;ORG UQ_CHARSET UNIQUE(ORG,INTERNAL_CHAR)
RULE_SETS RULE_CODE UNIQUE;RULE_TYPE;CONDITION JSON;ACTION JSON;PRIORITY DEFAULT 100;STATUS rule_type IN ('AUTH_CHECK','REWARD','FEE','INTEREST','DEDUCT','ELIGIBILITY');status IN ('ACTIVE','INACTIVE')

所有表均含 CREATED_AT/UPDATED_AT TIMESTAMP(6) WITH TIME ZONE(預設 SYSTIMESTAMP)、VERSION NUMBER(10) 樂觀鎖。


6. API 設計(來自 contracts/openapi/product-service.yaml)

端點 方法 說明 對應 FR
/api/v1/products POST 建立產品(CreateProductRequest→201;代碼重複 CC-PRD-1001) COM-6.2
/api/v1/products/{productCode} GET 單筆產品主檔(查無 CC-PLT-4040) COM-6.2
/api/v1/products/{productCode} PUT 更新產品(RETIRED 終態不可更新 CC-PRD-3002) COM-6.2
/api/v1/parameters/{namespace}/{paramKey} GET 查詢參數(選填 businessDate 依業務日取版本;查無 CC-PLT-4040) COM-6.1
/api/v1/parameters/{namespace}/{paramKey} PUT 設定參數(熱更新;生效日重疊 CC-PRD-1002) COM-6.1

錯誤碼沿用 common-domain ErrorCodes;org/valueType 列舉於應用層檢核(400 CC-PLT-4003,不落 DDL CHECK 變 500)。⚠️ copyFrom 之完整複製語意(§10)與狀態機端點屬後續增量。

6.1 參數版本鏈語意(C-2 定案)

操作 行為
PUT(新版本 [from, to?]) 自動封閉被覆蓋之現行版本(effective_to = from - 1);開放式版本被有尾端新版本切割 → 尾端自動延續(同值補 [to+1, ∞),避免參數憑空消失)
真重疊 較晚版本起日落入新區間內(以封閉後狀態重算)→ 400 CC-PRD-1002
查詢 businessDate(預設當日)取 covers(date) 之版本;查無 → 404 CC-PLT-4040

7. 關鍵邏輯

  • 參數生效區間:PARAMETERS 以 EFFECTIVE_FROM/EFFECTIVE_TO 管理版本,查詢須帶 AS_OF 時間取當時有效版本;UQ_PARAM 防止同鍵同起始日重複。
  • 組織碼對照:外部系統(如 VISA/MC 檔)交換前,經 CHARSET_MAPPINGS 轉內部碼,避免外部碼污染核心。
  • 規則評估:RuleEngine 依 PRIORITY 由小到大評估 RULE_SETS(條件 JSON 匹配 → 動作 JSON)。

7.1 參數變更事件輸出(product.parameter.changed.v1)——2026-10-01 新增(GOV-006 4.8 W-2 推廣・CR-027 批次 A-4)

緣由:SAD-002 §3.5 明列本服務發布 product.parameter.changed.v1(用途:參數熱更新),
SDD-006 §4.2 亦已定義 payload,惟從未有人發射(同 SCS-002 §7.5 之型)。
本節補生產端。

主題:cc.product.parameter.changed(命名規則 SAD-004 §5.4)。

項目 值
eventType product.parameter.changed.v1
Outbox aggregate_type product
Outbox event_type product.parameter.changed(不含版本)
Kafka 分區鍵 namespace + "/" + paramKey

payload(v1;欄位名依 SDD-006 §4.2):namespace/paramKey/effectiveFrom。

處理:ParameterService.set 於 repository.save(version) 後、同一交易寫 outbox。

設計要點:

  • ★ 分區鍵為 namespace/paramKey(而非版本列之 id):本事件之聚合是「該參數」,
    非某個版本——同一參數之歷次變更須落同一分區以保序(SAD-004 §5.4)。
  • ★ 補上 @Transactional(2026-10-01,#A-130):原 set() 未標交易——惟其步驟
    (封閉現行版本 +(必要時)補延續版本 + 寫新版本)本應為原子操作;
    且 Outbox 寫入必須與權威寫入同交易,否則失去 Transactional Outbox 之意義(SDD-001 §3.15)。
  • ⚠️ 消費者為「全體服務」(SAD-002 §3.5)——消費端尚未實作(批次 C)。
    故現階段為「有人發、沒人收」(如實揭露)。
  • ★ 首案消費端規格已立:limit-service 之商品參數消費閉環見 SCS-002 §7.14(CR-045 D4,2026-10-15 採納立場;規格先行,程式尚未實作)。本節(生產端)不變。

真機驗證(2026-10-01;真實 Oracle + Kafka):見 §10 #6。


8. 外部依賴與降級

依賴 失敗處理(Stand-in)
僅依賴自身 CC_PRODUCT 權威庫;屬讀多寫少之配置型服務 無強即時外部依賴;快取(Redis 8)可承接高頻查詢,失效時回查 DB,不降級功能

9. 測試案例(對應 TRP-001)

18 tests, 0 failures(2026-09-22,--rerun-tasks 實測)

測試類別 案例數 對應
ProductServiceTest 6 建立 DRAFT/代碼重複 CC-PRD-1001/org 非法 400/查無 404/更新版本遞增
ParameterServiceTest 8 依業務日取版本/自動封閉/真重疊 CC-PRD-1002/同生效日/查無 404/valueType 檢核/切割尾端延續
LayeringTest(ArchUnit) 4 CSP-002 §6.3

§9.3 真機 e2e(8085,使用者 token):產品 201→200→PUT 200(org 更新);重複代碼 CC-PRD-1001;參數版本鏈 09-21→404(生效前)、09-22→3(v1)、09-26/10-14→9(v2 切割)、10-16/11-01→3(尾端延續)——DB 與 API 一致。


10. 已知限制(誠實揭露)

  • PRODUCTS.status 轉換未建模為 SDD-005 正式狀態機,目前以 CHECK 約束+應用層邏輯控管;若客戶要求正式狀態機,須提 CR 補 SDD-005。
  • RuleEngine 之規則語意範圍(哪些 FR 實際消費 RULE_SETS)待各消費側服務(授權/紅利/費用)確認後凍結。
  • product-service 骨架與埠號尚未產生 → ✅ 已過時:埠 8085/CC_PRODUCT schema/Flyway V1 皆已就緒,C-2 已實作(1.1)。
  • copyFrom 之完整複製語意(來源產品參數複製範圍)本期僅驗證來源存在,未實作複製;產品狀態機端點(DRAFT→ACTIVE)屬後續增量。
  • product.parameter.changed.v1 之消費者尚未實作(§7.1,#A-130):SAD-002 §3.5 載其消費者為全體服務(參數熱更新)——無任何服務訂閱 → 現階段為「有人發、沒人收」(如實揭露)。★ 首案(同步取參)消費端規格見 SCS-002 §7.14(CR-045 D4,規格中;程式待立 SCS 後實作)。
  • 真機驗證(2026-10-01,#A-130;真實 Oracle + Kafka):PUT /api/v1/parameters/{ns}/{key}(X-Channel/X-Idempotency-Key 必填)→ HTTP 200 → outbox 落列(product.parameter.changed、aggregate_id=e2e-ns/event-test、published=YES)→ Kafka 主題 cc.product.parameter.changed 自動建立;消費得信封,payload 與 SDD-006 §4.2 逐欄一致(namespace/paramKey/effectiveFrom)。併註:實作時補上 @Transactional(原方法未標交易——版本鏈之「封閉現行/補延續/寫新版本」本應原子,且 Outbox 須與權威寫入同交易,SDD-001 §3.15)。

★ 缺陷修正(2026-10-01,真機驗證時發現)

paramValue 之 JSON 序列化錯誤 → ORA-40441 → HTTP 500

CC_PRODUCT.parameters.param_value 為 JSON 欄。原實作以 paramValue.toString() 直接寫入:

  • valueType=STRING + paramValue="v1" → 寫出裸字串 v1 → 非法 JSON → ORA-40441 → HTTP 500
    (實測:PUT /api/v1/parameters/e2e-ns/topic-test 回 500 CC-PLT-5000)
  • valueType=JSON + {"a":1} → Map.toString() 產生 {a=1} → 非法 JSON(同型缺陷)

修正:一律以 Jackson 序列化(ParameterService.toJson)——
"v1"/100/true/{"a":1}/[1,2] 皆為合法 JSON。
回歸測試:ParameterValueJsonTest 6 例(含特殊字元轉義)。

⚠️ 揭露:本缺陷非 CR-030 之 12 項之一,係於驗證 #15(主題命名)時順帶發現——
原請求以 valueType=AMOUNT 可繞過(整數 toString() 恰為合法 JSON),故長期未被察覺。

11. 修訂記錄

版本 日期 修訂內容
1.4 2026-10-15 CR-045 D4(首案消費閉環):於 SCS-005 §7.1/§10 #A-130 新增正向參照至 SCS-002 §7.14(首案商品參數消費端規格已立;程式尚未實作)。本服務(生產端)內容不變
1.3 2026-10-01 WorkBuddy AI
1.0 2026-09-18 規格先行版(埠號與類別名為規劃值)
1.2 2026-10-01 GOV-006 4.8 W-2 推廣・CR-027 批次 A-4:新增 §7.1 參數變更事件輸出(product.parameter.changed.v1)——SAD-002 §3.5 明列本服務發布、SDD-006 §4.2 已定義 payload,惟從未有人發射。實作:V2__outbox.sql + OutboxPort/ParameterEventFactory/JdbcOutboxRepository/{KafkaEventPublisher,OutboxRelayConfig,OutboxRelayScheduler};ParameterService.set 同交易寫 outbox。★ 分區鍵=namespace/paramKey(聚合為「該參數」,非版本列)。★ 併補 @Transactional(原方法未標交易;版本鏈本應原子,且 Outbox 須同交易)。真機驗證通過(payload 與契約逐欄一致)。⚠️ 消費者(全體服務)尚未實作
1.1 2026-09-22 依 C-2(BEAM PMO 指示,GOV-006 排序) 實作 5 端點:產品 CRUD+參數版本化(熱更新)。版本鏈語意定案(§6.1):自動封閉被覆蓋版本/真重疊 CC-PRD-1002/開放式被切割 → 尾端自動延續(同值補 [to+1, ∞),避免參數憑空消失)。單測 18 例 0 失敗;真機 e2e(產品 201/200/PUT、參數版本鏈多日期點、DB 一致);三支掃描器 0 命中。⚠️ copyFrom 完整語意與狀態機端點屬後續
(15-15/67)