跳至主要内容

ADR-011: 以獨立 server 模組表達資料隔離取向

ADR 編號: 011 狀態: 已接受 (Accepted) 決策日期: 2026-07-27 決策者: AppFuse Team + AI Assistant 取代: ADR-004ADR-009 範圍: 應用 server 參考實作、Feature、practices、scaffold 與 fleet 路由


摘要

app-server 拆為兩個可獨立建置、測試與演進的參考實作:

  • app-tenant-server,模組 type 為 server-tenant
  • app-tenantless-server,模組 type 為 server-tenantless

資料隔離取向由模組結構與 type 表達,不再由 project.json.defaultDataIsolation 或 runtime mode 切換。舊 variants/ overlay、variant-assembly.pyvariantCheckapp.storage.partition-source 一併退場。

背景

隔離 variant 曾以最小 overlay 降低重複,但它也把兩種產品形狀綁在同一個 source: Feature catalog、practices、測試與出貨閘門都必須理解 variant。當 tenant 與 tenantless 開始擁有不同 Feature 內容與演進方向時,overlay 已不再是單純 representation 細節, 而是跨產品的隱性耦合。

決策

1. Feature 屬於模組

兩個 server 各自擁有 .claude/features.json。同一概念的 Feature 應盡可能保持相同名稱 與操作方式,方便說明與使用;但 catalog 內容、requires、seams、reference domains 與實作允許完全不同。不得假設同名 Feature 可跨 type 同步。

2. Practices 屬於模組 type

兩個 server 各自是 Tier 3 practices source:

  • server-tenant fleet 只向同 type 下游 publish
  • server-tenantless fleet 只向同 type 下游 publish

兩個 fleet 不交叉 publish/reflow,也不建立共同的 app-server practices 層。 可保持規則名稱一致,但內容由各 type 自行作者化。

3. 不再需要 defaultDataIsolation

project.json.modules[].type 已足以選擇正確 source。/scaffold-project/scaffold-module/feature/practices 與 server 相關 skill 皆依 type 路由, 不再讀取或寫入 defaultDataIsolation

4. Tenantless 只承諾不依賴 tenant runtime

app-tenantless-server 不得依賴 TenantContextROOT_TENANT、 Hibernate tenant resolver、@TenantIdTenantAwareEntitytenantless 不等於 ownership,也不替所有 Entity 預設 owner;各功能可依需求採 application scope、 使用者範圍、組織或資源擁有權,並由自己的 repository query 與 service policy 明確表達。 固定 storage partition 是 namespace,不是 tenant sentinel。

app-tenant-server 則自行擁有 tenant Feature、tenant context、@TenantId entity 與租戶隔離測試。

5. 共用邊界只到 appfuse-server

不新增 app-common-server。可真正跨產品共用的能力必須保持 tenant-neutral, 由 appfuse-server 提供 primitive/SPI;應用組裝、entity、政策與 Feature 留在各自模組。

6. 兩棵 server 都必須是可運行的業務參考實作

分家不是只複製 Feature 組裝樹。app-tenantless-server 必須有自己的花店業務切片, 示範 customer、product、order、dashboard。目前為避免過度設計,這組範例採 application scope,不加入 ownerId 或模組級 ownership policy。領域與端點名稱可和 tenant 版保持 對稱以降低學習成本,但資料模型與行為不要求相同,也不得引入 tenant runtime primitive。

後果

  • 兩個 server 可獨立建置、發布、裁剪 Feature 與演進 practices。
  • 同名 Feature 的文件與操作體驗可維持一致,但同步權只存在於同 type fleet。
  • 移除 variant 組裝與 runtime mode 後,失效狀態與出貨閘門顯著減少。
  • 兩種 type scaffold 後都取得可啟動、可操作的業務參考實作;tenantless 版不是只有 Feature 附屬 reference domains 的空殼。
  • 中性程式碼若只在兩個 app 模組間手動複製,仍可能漂移;只有符合框架邊界者才可上收 appfuse-server

驗證

  • app-tenant-server: Feature inventory I3 + ./gradlew build
  • app-tenantless-server: Feature inventory I3 + ./gradlew build
  • tenantless source 掃描不得出現 tenant runtime primitive
  • tenantless 花店測試覆蓋 application-scoped collection、無 owner context 的讀取與訂單關聯
  • workspace 不得再有 defaultDataIsolation、variant assembly 或舊 app-server 模組