# 04 · 值对象与上下文按模块化:DataVisibility / DataScopeLevel / DataVisibilityContext Type: grilling Status: resolved Blocked by: 01 ## Question `DataVisibility`(现承载单一 level + 部门集合)、`DataScopeLevel`、`DataVisibilityContext` (ThreadLocal 放一份 `DataVisibility`)如何改造成「按模块」的结构? 要定的点: - 上下文形态:ThreadLocal 从「一份 DataVisibility」变成「module → DataVisibility 的 map」? 还是「一份持有各模块档位的聚合对象」? - 部门集合、expandedDeptIds(子树展开)是否可跨模块共享(同一用户部门集合不随模块变,仅档位变)—— 这决定改造能否只在「档位」维度分模块、而部门集合仍算一次。 - `DataVisibility` 的不可变/整体装入契约(ADR-0006「不存在设了一半」)如何在多模块下保持。 - `DataScopeLevel` 枚举本身是否需要动(大概率不动,仅其"每模块一份")。 - `currentScope()` / `currentOwnership()` 的签名如何变(读侧需按模块问 scope)。 依赖 01(模块标识)。与 05(拦截器)互相牵连,但结构决策在此先定。 ## Answer ### 方案 A:一个 DataVisibility 持有 `Map` #### 1. DataVisibility 结构变化 把单个 `level` 字段替换为 `Map moduleLevels`(key = module code,value = 档位)。 身份数据不变,只存一份: ```java public final class DataVisibility { private final Long userId; // 不变 private final Long primaryDeptId; // 不变 private final List deptIds; // 不变 private final List expandedDeptIds; // 不变 private final Map moduleLevels; // 新:替代单个 level } ``` - `Map.copyOf(moduleLevels)` 封装,不可变。 - 空构造校验:`userId` 必填;`moduleLevels` 必填但**允许空 Map**(空 = 所有模块走默认 SELF)。 - `expandedDeptIds` 校验放宽:只要任一模块的 level 是 DEPT_AND_CHILDREN 就要求非 null(空 List 可以,表示一律不可见)。 #### 2. 方法签名变化 ```java // 读侧:按模块取档位 → 折算 VisibilityScope public VisibilityScope visibilityScope(String moduleCode) { DataScopeLevel level = moduleLevels.getOrDefault(moduleCode, DataScopeLevel.SELF); return switch (level) { case SELF -> VisibilityScope.ownedBy(userId); case DEPT -> departmentsOrNone(deptIds); case DEPT_AND_CHILDREN -> departmentsOrNone(expandedDeptIds); case ALL -> VisibilityScope.allVisible(); }; } // 写侧:不变 public DataOwnership dataOwnership() { ... } // 完全不动 ``` #### 3. DataVisibilityContext 变化 ThreadLocal 仍持有一份 DataVisibility,但 `currentScope()` 加参数: ```java public static VisibilityScope currentScope(String moduleCode) { DataVisibility visibility = CURRENT.get(); return visibility != null ? visibility.visibilityScope(moduleCode) : VisibilityScope.allVisible(); } // currentOwnership() 完全不变 public static Optional currentOwnership() { ... } ``` #### 4. 部门集合只算一次 `deptIds` / `expandedDeptIds` 是用户级的(主部门 + 兼职并集 + 子树展开),与模块无关。 PermissionResolver 算一次,存入 DataVisibility。各模块的档位决定**用不用**这组部门 ID,不需要**重算**。 #### 5. DataScopeLevel 枚举不动 四个值(SELF/DEPT/DEPT_AND_CHILDREN/ALL)不变,每模块各选一个。枚举本身无需改。 #### 6. ADR-0006(不存在设了一半)保持 DataVisibility 仍不可变、构造成功即完整。Map 用 `Map.copyOf` 封装。空 Map 是合法状态(所有模块走 SELF)。不存在「设了一半」——要么整个上下文没有(无登录),要么整个上下文有(带完整的 moduleLevels map)。