跳至主要内容

ADR-026: 固定 application adapter 歸 Feature 所有

ADR 編號: 026 狀態: 已接受 (Accepted) 決策日期: 2026-07-25 決策者: Development Team 取代: ADR-024 中「Feature 只含組態接合」 與「具體端點一律屬 reference implementation」的過度推論


摘要

Feature 的預期修改面仍限於組態接合,但 Feature 的內容不限於 *Config*Properties。只依賴 capability、契約固定、沒有合理 per-project 重塑需求的 Spring application adapter(例如 Actuator endpoint、health/info contributor、固定系統握手 REST endpoint 及其授權)是零 drift 的 canonical core,應隨 Feature 同步。

Reference implementation 的判準是「預期由消費端依產品資源模型重塑」,不是「可以整組 不安裝」。Feature 本身就是可選的;可選性不能用來把 Feature 的固定 surface 降級為 reference domain。


背景

ADR-024 正確收斂了 seam:entity、repository、CRUD、產品 wire shape 與角色詞彙不該為了 保留客製權而留在 Feature。它們預期會 drift,應住業務層 reference domain。

但後續實作把「Feature 內不得有預期修改的非組態檔」錯推成「Feature 內只能有組態檔」, 再把所有具體 URL、Actuator endpoint、health 與授權視為 reference implementation。這混淆了 兩件事:

  1. 整個 Feature 是否安裝:Feature 的生命週期選擇。
  2. Feature 安裝後某個檔是否預期被改寫:core 與 reference implementation 的 ownership 判準。

MailEndpoint 是反例:它只操作 RoutingMailerDirectMailer,不認識 MailSetting、 tenant、repository 或產品 schema;其 instance-scoped 維運契約沒有合理的專案變異。 把它移到 controller/mail 不會增加消費端真正需要的宣告權,只會使 canonical adapter 失去 /feature sync

ConfigCheckEndpointSystemInfoController 同型:兩者只呈現 ConfigurationDiagnosticsSystemInfo capability,沒有 platform-info 產品資源模型可供 retarget。為它們建立 platforminfo reference domain 是虛構的生命週期。


決策

決策一:三層定位改以「能力/固定整合/產品資源」區分

擁有什麼預期 drift
capability可跨應用重用的能力本體與 SPI0;以 Maven 版本同步
Feature corecapability 無法在 jar 內完成的固定 application integration0;以 /feature sync 同步
reference implementation預期依產品資源模型重塑的可運行起點必然;retarget 或 prune

Feature core 可包含:

  • bean/config fragment 組裝;
  • Actuator @WebEndpoint
  • HealthIndicatorInfoContributor
  • 固定的 framework-to-application adapter;
  • 與上述 surface 同進退的 authority、authority contributor、security contributor;
  • 固定且不承載產品資源決策的 REST handshake。

決策二:canonical application adapter 的必要條件

一個非組態檔只有同時滿足以下條件,才屬 Feature core:

  1. 只依賴 capability、Spring integration API 或同 Feature core;
  2. 不依賴 reference entity、repository、service、tenant scope 或產品詞彙;
  3. wire/維運契約是該 Feature 的固定語意;
  4. 消費端沒有已知且合理的逐專案重塑需求;
  5. Feature 移除時,該 adapter 與其授權應一起消失。

只要依賴產品資源形狀,或路徑/操作粒度本來就由產品決定,就屬 reference implementation。 @WebEndpoint@RestController annotation 本身不是分類判準。

對一般使用者/SPA 可達的 /api/** REST surface,採 reference-first: 路徑可設定不代表 wire、操作粒度與 authorization 已中性化。只有受到外部標準完整約束, 或已有實證顯示消費端不需要重塑的 system handshake,才能例外留在 Feature core。

決策三:seam 規則不變,但不得反推 Feature 只能含 seam

seams 仍只登錄消費端擁有的組態接合點。Canonical adapter 是 core,不是 seam;下游不應 直接修改。若需要更嚴的存取控制,可由較高優先序的 SecurityContributor 疊加,不必 fork endpoint。

若第二個下游出現相同的 adapter 變異需求,再抽設定或 SPI;不得為想像中的變異預先把固定 surface 降級 reference implementation。

決策四:授權跟隨 surface owner

Feature 擁有 canonical surface 時,authority 名、authority contributor 與 security contributor 一起留在 Feature。專案仍透過角色/外部身分來源決定誰取得 authority。

Reference resource 的 authority 則留在 reference domain,prune 資源時一併消失。

決策五:修正 mail 與 platform-info

Feature coreReference implementation
MailConfigMailPropertiesMailSetting entity
MailEndpointMailHealthIndicatorrepository、service、provider adapter
MailAuthorityMailAuthorityContributorMailSecurityContributorMailSettingController、create DTO、mail_setting:* authority
PlatformInfoConfigConfigCheckProperties
ConfigCheckEndpointSystemInfoController
PlatformInfoAuthority 與兩個 contributor

platform-info 因此不再宣告 referenceDomains: ["platforminfo"]mail 仍保留 referenceDomains: ["mail"],因為 MailSetting 資源實作確實可 retarget/prune。

audit 無需變更:它提供 AuditEventRepository 後啟用 Spring Boot 內建 /actuator/auditevents,其 authority 與 security contributor 已正確留在 Feature; 持久 repository adapter 則留在 audit reference domain。

2026-07-27 修訂:staging REST adapter 降級 reference implementation

StagingFileController 雖只依賴 FileStorage capability,但仍決定 prepare/PUT/multipart/batch/exists/metadata/delete 的 HTTP shape,並固定 isAuthenticated() authorization。把 RequestMapping 外部化只交還 path prefix, 沒有交還操作集合、DTO、錯誤契約與授權,因此不符合必要條件 3、4。

修訂後:

  • feature/file 保留 backend wiring、partition seam、staging lifecycle 與唯一 cleanup scheduler;
  • controller/file reference domain 擁有 staging REST adapter、multipart/batch 設定、 request/response mapping、錯誤與 authorization;
  • FileStorage.StagingUploadInfo 只在 Presigned/SAS 等 backend direct upload 時帶 URL; application upload URL 由 reference adapter 依自己的 mapping 與 tempId 建立。

Reference ownership 已提供 source-level retarget 能力,不代表 mapping 還要成為 runtime 設定。預設 path 直接寫在 controller annotation,採一般 Spring application 最容易辨識的 佈局;整合測試負責檢查 mapping 與 application upload URL 一致。只有同一 binary 確實需要 依部署環境切換 path 時,才應新增 mapping 設定;反向代理或 context path 的部署差異不由此 API contract 處理。

這也補強一項判準:只依賴 capability 是必要條件,不足以單獨證明 adapter canonical。 對外 wire 是否預期由消費端重塑,仍是 ownership 的決定條件。


後果

正面

  • 固定 Actuator/health/system handshake 可繼續由 /feature sync 修正。
  • Reference domain 只代表真正存在的產品變異,不再為固定 adapter 虛構業務域。
  • endpoint、authority 與 security rule 維持同一生命週期。
  • ADR-024 的核心成果保留:entity、repository、CRUD 與產品 wire 不回流 Feature。

代價

  • Feature core 不再能用「只看 *Config*Properties」機械辨識,code review 必須核對 canonical adapter 五條必要條件。
  • Canonical surface 一旦發布便需維持相容;若日後證實有重複變異需求,須正式抽設定或 SPI。

遷移

  1. mail 的 endpoint、health、mail:ops authority 與 security contributor 移回 feature/mail
  2. platform-info 的兩個 endpoint 與授權三件套移回 feature/platforminfo
  3. platform-info 從 catalog 移除 referenceDomains
  4. 更新 feature surface 規則、guide、inventory 與 variant 組裝測試。

相關