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。這混淆了 兩件事:
- 整個 Feature 是否安裝:Feature 的生命週期選擇。
- Feature 安裝後某個檔是否預期被改寫:core 與 reference implementation 的 ownership 判準。
MailEndpoint 是反例:它只操作 RoutingMailer/DirectMailer,不認識 MailSetting、
tenant、repository 或產品 schema;其 instance-scoped 維運契約沒有合理的專案變異。
把它移到 controller/mail 不會增加消費端真正需要的宣告權,只會使 canonical adapter
失去 /feature sync。
ConfigCheckEndpoint 與 SystemInfoController 同型:兩者只呈現
ConfigurationDiagnostics/SystemInfo capability,沒有 platform-info 產品資源模型可供
retarget。為它們建立 platforminfo reference domain 是虛構的生命週期。
決策
決策一:三層定位改以「能力/固定整合/產品資源」區分
| 層 | 擁有什麼 | 預期 drift |
|---|---|---|
| capability | 可跨應用重用的能力本體與 SPI | 0;以 Maven 版本同步 |
| Feature core | capability 無法在 jar 內完成的固定 application integration | 0;以 /feature sync 同步 |
| reference implementation | 預期依產品資源模型重塑的可運行起點 | 必然;retarget 或 prune |
Feature core 可包含:
- bean/config fragment 組裝;
- Actuator
@WebEndpoint; HealthIndicator、InfoContributor;- 固定的 framework-to-application adapter;
- 與上述 surface 同進退的 authority、authority contributor、security contributor;
- 固定且不承載產品資源決策的 REST handshake。
決策二:canonical application adapter 的必要條件
一個非組態檔只有同時滿足以下條件,才屬 Feature core:
- 只依賴 capability、Spring integration API 或同 Feature core;
- 不依賴 reference entity、repository、service、tenant scope 或產品詞彙;
- wire/維運契約是該 Feature 的固定語意;
- 消費端沒有已知且合理的逐專案重塑需求;
- 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 core | Reference implementation |
|---|---|
MailConfig、MailProperties | MailSetting entity |
MailEndpoint、MailHealthIndicator | repository、service、provider adapter |
MailAuthority、MailAuthorityContributor、MailSecurityContributor | MailSettingController、create DTO、mail_setting:* authority |
PlatformInfoConfig、ConfigCheckProperties | 無 |
ConfigCheckEndpoint、SystemInfoController | 無 |
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/filereference 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。
遷移
- mail 的 endpoint、health、
mail:opsauthority 與 security contributor 移回feature/mail。 - platform-info 的兩個 endpoint 與授權三件套移回
feature/platforminfo。 - platform-info 從 catalog 移除
referenceDomains。 - 更新 feature surface 規則、guide、inventory 與 variant 組裝測試。