跳至主要内容

ADR-004: 方法論對資料隔離策略中性化

ADR 編號: 004 狀態: 已接受 (Accepted) 決策日期: 2026-06-21 決策者: AppFuse Team + AI Assistant 範圍: docs-methodology(開發方法論)


摘要

AppFuse 方法論原本把多租戶寫死成預設:m-server-common.md 規定「業務 Entity 必須繼承 AuditableTenantEntity」、/domain-model/us/epic/api-spec 在產出時一律假定租戶隔離。結果是——單租戶專案即使跑了 /remove-multi-tenancy(限 server 模組)把程式碼轉乾淨,方法論層仍會反覆「再生」多租戶痕跡:下次跑 /domain-model 又建議 AuditableTenantEntity/epic 又長出「多租戶考量」段。

本 ADR 將方法論對「資料怎麼隔離」中性化:不偏袒租戶或擁有權任一模式,改由 project.jsondefaultDataIsolation 欄位作為單一事實來源宣告專案取向,消費此欄位的 skill 讀它分流。隔離策略回歸其本來面目——per-entity 的決策(即使多租戶專案,認證層 Entity 也免租戶),defaultDataIsolation 只設定業務 Entity 的預設,個別 Entity 可覆寫。


背景 (Context)

問題陳述

框架 jar(appfuse-server)本身已經中性——它同時提供 AuditableTenantEntity(租戶隔離)與 AuditableBase(無租戶欄位),兩者平等、不強制。非中性的不是框架碼,而是方法論的預設建議

再生源頭寫死的內容
m-server-common.md 義務表「業務 Entity 必須繼承 AuditableTenantEntity」——硬義務
/domain-model候選實體預設推 AuditableTenantEntity 基類
/epic/us內建「多租戶考量」範本段與澄清提問
/api-spectenant_id 注入註記、不可更新欄位含 tenantId

這些把「per-entity 的隔離決策」硬塞成「project 全域的多租戶預設」。

核心張力

單租戶專案的轉換工具 /remove-multi-tenancy 限 server 模組(見其「範圍邊界」):它清得了程式碼與模組 rule,但碰不到 workspace 層的共享方法論m-server-common.md/domain-model 等 skill)。於是:

  • 清乾淨的程式碼,會被下次的 /domain-model/epic 依「多租戶預設」重新污染
  • 若改採「逐檔在 scaffold 副本塞單租戶 banner」止血,等於把單一專案的決策 fork 進通用方法論檔,且真相散在 N 個 banner、漏一個就再生。

缺的是:一個讓方法論不偏袒、由專案宣告取向的機制。

關鍵識別:隔離是 per-entity,不是全域二元

即使在「多租戶」專案,認證層 Entity 也繼承 AuditableBase(免租戶)——m-server-common.md 自己就寫了「認證層 Entity 例外」。所以租戶性本來就是 per-entity 的屬性;一個「單租戶專案」只是所有業務 Entity 的隔離答案剛好一致為「無租戶」。

這意味著「全域 tenant on/off flag」是個洩漏的抽象——它假裝租戶性是全域,與框架實際的 per-entity 處理相左。

限制條件

  • /domain-model 的澄清循環已經在問「此實體是業務層還是認證層」來選基類——擴成「隔離策略軸」是延伸既有機制,非新增負擔。
  • 多數使用框架的專案是多租戶 SaaS——預設值必須是 tenant 以保留現行行為、向後相容(欄位缺省=tenant)。
  • 花店參考實作(app-server)須維持多租戶,作為租戶路徑的可運行 demo。

考量的方案 (Options Considered)

方案 A:逐檔塞單租戶 banner(止血)

在每個 scaffold 出的專案副本,手動於 m-server-common.md/us/domain-model 等加「本專案單租戶」banner。

  • ✅ 不動框架、立即可行
  • ❌ 污染共享方法論檔;真相散在 N 個 banner、漏一個就再生;每個單租戶專案重複 fork

方案 B:全域 tenancy flag(tenancy: multi|single

project.json 加一個全域開關,skill 讀它二分。

  • ✅ 單一事實來源、一處改全對齊
  • ❌ 「全域二元」是洩漏抽象——假裝租戶性是 project 全域,與框架的 per-entity 處理(認證層免租戶)相左

方案 C:中性方法論 + per-entity 隔離策略宣告(採用)

方法論對兩種隔離模式中性;project.json 宣告預設隔離策略 defaultDataIsolationtenantownership),作為業務 Entity 的預設基類來源;/domain-model 沿既有澄清循環逐 Entity 套用、per-entity 可覆寫。

  • ✅ 單一事實來源;忠於框架的 per-entity 模型(預設+覆寫,而非全域開關)
  • ✅ 方法論檔保持中性,對 multi/single 皆成立,零 per-file banner
  • /remove-multi-tenancy 退化為「翻預設 + 批次重宣告」,不再需要動共享方法論
  • ⚠️ 需要同步改動數個方法論 skill/rule(一次性成本)

決策 (Decision)

方案 C。具體:

  1. project.json 新增 defaultDataIsolation"tenant"(預設,向後相容)/ "ownership"。語意權威定義落在 m-server-common.md 的「資料隔離策略」節。
  2. m-server-common.md 中性化:義務表「必須繼承 AuditableTenantEntity」改為依 defaultDataIsolation 分流;新增「資料隔離策略」節為單一事實來源,列出消費此欄位的 skill。
  3. /domain-model 擴成隔離策略軸:業務 Entity 預設基類讀 defaultDataIsolation,澄清循環逐 Entity 釐清(業務/認證層 × 租戶/擁有權),per-entity 可覆寫。
  4. /epic/us/api-spec 中性化:「多租戶考量」→「資料隔離考量」(涵蓋租戶/擁有權兩路);tenant_id 註記改條件性適用。
  5. /scaffold-project 收集 defaultDataIsolation:建立新專案時詢問並寫入。
  6. /remove-multi-tenancy 補完:Step 4i 由「點名清單」改「系統化 grep 整個 {module}/.claude/」(修復 10-framework13-transaction 等漏網);新增 Step 4j 翻轉 project.jsondefaultDataIsolationtenantownership)。

「預設值」與「中性」不衝突:方法論讀欄位分流(中性),欄位本身有合理預設 tenant(向後相容+多數場景零摩擦)。預設值不等於方法論硬編。

未跳過 RFC:本決策由唯一框架維護者直接落地(RFC 的價值是與消費者/其他維護者社會化,無對象則為負擔),但保留決策記錄——即本 ADR。


後果 (Consequences)

正面

  • 單租戶專案跑完 /remove-multi-tenancy 後,方法論不再再生多租戶痕跡(/domain-modelownership 建議 AuditableBase)。
  • 共享方法論檔保持中性、不被任何專案 fork,框架升級時無 per-project banner 雜訊。
  • 隔離策略的心智模型對齊框架實況(per-entity,預設+覆寫)。

負面/成本

  • 一次性改動數個方法論 skill/rule(已於本次落地)。
  • 多租戶專案的文檔段落由「多租戶考量」更名為「資料隔離考量」——既有 reference 文檔的措辭與新範本不完全一致(不影響功能,漸進對齊)。

中性

  • 花店參考實作維持多租戶(defaultDataIsolation: tenant),程式碼不動。
  • 框架 jar 不變——本 ADR 純屬方法論層,無框架 API 變更。

相關文檔