ADR-011: 以獨立 server 模組表達資料隔離取向
ADR 編號: 011 狀態: 已接受 (Accepted) 決策日期: 2026-07-27 決策者: AppFuse Team + AI Assistant 取代: ADR-004、ADR-009 範圍: 應用 server 參考實作、Feature、practices、scaffold 與 fleet 路由
摘要
app-server 拆為兩個可獨立建置、測試與演進的參考實作:
app-tenant-server,模組 type 為server-tenantapp-tenantless-server,模組 type 為server-tenantless
資料隔離取向由模組結構與 type 表達,不再由 project.json.defaultDataIsolation
或 runtime mode 切換。舊 variants/ overlay、variant-assembly.py、variantCheck
與 app.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-tenantfleet 只向同 type 下游 publishserver-tenantlessfleet 只向同 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 不得依賴 TenantContext、ROOT_TENANT、
Hibernate tenant resolver、@TenantId 或 TenantAwareEntity。tenantless 不等於
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 buildapp-tenantless-server: Feature inventory I3 +./gradlew build- tenantless source 掃描不得出現 tenant runtime primitive
- tenantless 花店測試覆蓋 application-scoped collection、無 owner context 的讀取與訂單關聯
- workspace 不得再有
defaultDataIsolation、variant assembly 或舊app-server模組