3. 缓存领域模型(一):缓存实体(Cache Entity)与值对象(Value Object)的建模

在 OpenAPI 场景下,缓存一致性治理的第一步是建立精确的领域模型。许多团队陷入缓存混乱的根本原因,是将“缓存”视为一个技术工具,而非领域概念。本节从领域驱动设计(DDD)的视角,将缓存抽象为缓存实体(Cache Entity)与值对象(Value Object),并给出明确的建模边界与治理规则。

3.1 为什么缓存需要领域建模?

传统缓存设计往往直接面向 Redis Key-Value 或本地 Map,导致以下问题:

  • 身份模糊:同一个业务对象在缓存中可能被多个 Key 表示,无法追踪其生命周期。
  • 变更失控:缓存更新时只更新了值,未更新标识,导致脏数据。
  • 一致性缺失:没有区分“可替换的值”与“需追踪的实体”,缓存失效策略混乱。
核心原则:缓存中的每一个数据项,都应明确其领域角色——它是需要被追踪身份的“实体”,还是仅作为快照的“值对象”。

3.2 缓存实体(Cache Entity)

定义:缓存实体是具有唯一标识(Cache ID)且生命周期需要被追踪的缓存对象。即使其属性值发生变化,该对象在缓存中的身份保持不变。

3.2.1 建模特征

特征 说明 OpenAPI 场景示例
唯一标识 每个实体拥有全局唯一的 Cache ID(如 user:uuid:123) order:orderId:ORD20250301
可变属性 属性值可以更新,但标识不变 订单状态从“待支付”变为“已支付”
生命周期管理 需要显式创建、更新、删除操作 用户注销后,缓存实体需主动失效
一致性责任 实体变更时,必须同步更新或失效缓存 数据库订单状态变更 → 触发缓存实体更新

3.2.2 代码建模示例

// 缓存实体:具有唯一标识的缓存对象
public class CacheEntity<T> {
    private final String cacheId;          // 全局唯一标识
    private final String entityType;       // 实体类型(如 "user", "order")
    private T data;                        // 业务数据
    private long version;                  // 乐观锁版本号
    private Instant lastModified;          // 最后修改时间

    public CacheEntity(String cacheId, String entityType, T data) {
        this.cacheId = cacheId;
        this.entityType = entityType;
        this.data = data;
        this.version = 1L;
        this.lastModified = Instant.now();
    }

    // 更新数据时版本号递增
    public void updateData(T newData) {
        this.data = newData;
        this.version++;
        this.lastModified = Instant.now();
    }

    // 通过 cacheId 判断身份是否相同
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CacheEntity<?> that = (CacheEntity<?>) o;
        return Objects.equals(cacheId, that.cacheId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(cacheId);
    }
}

3.3 缓存值对象(Cache Value Object)

定义:缓存值对象是没有唯一标识、仅通过属性值来定义自身的缓存对象。两个值对象如果所有属性值相同,则视为同一个对象。

3.3.1 建模特征

特征 说明 OpenAPI 场景示例
无标识 不持有 Cache ID,仅由属性值组成 Address{city, district, detail}
不可变性 创建后不应修改,需要变更时直接替换整个对象 用户地址变更 → 替换整个地址值对象
值相等性 通过所有属性比较判断是否相等 两个 Address 对象属性完全一致则视为相同
共享安全 可被多个实体引用,无需担心副作用 多个订单共享同一个收货地址值对象

3.3.2 代码建模示例

// 缓存值对象:无标识,不可变,通过值比较
public final class CacheValueObject<T> {
    private final T data;                  // 业务数据
    private final Instant cachedAt;        // 缓存时间戳

    public CacheValueObject(T data) {
        this.data = data;
        this.cachedAt = Instant.now();
    }

    public T getData() {
        return data;
    }

    public Instant getCachedAt() {
        return cachedAt;
    }

    // 值对象通过所有属性比较
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CacheValueObject<?> that = (CacheValueObject<?>) o;
        return Objects.equals(data, that.data);
    }

    @Override
    public int hashCode() {
        return Objects.hash(data);
    }
}

3.4 实体 vs 值对象:选择决策矩阵

判断维度 选择缓存实体 选择缓存值对象
是否需要追踪变更历史? 是 否
数据是否会被独立更新? 是 否(整体替换)
多个消费者是否共享同一份数据? 是(需同步) 是(无副作用)
缓存失效是否需要精确控制? 是(按 ID 失效) 否(按 Key 整体失效)
典型 OpenAPI 场景 用户会话、订单状态、商品库存 配置信息、静态字典、计算快照
实践建议:在 OpenAPI 网关层,用户令牌(Token)应建模为缓存实体(需追踪登录状态),而接口限流配额应建模为缓存值对象(仅需当前值)。

3.5 缓存领域模型关系图

下图展示了缓存实体与值对象在缓存层中的协作关系:

缓存领域模型:实体与值对象协作关系 缓存实体(Cache Entity) cacheId: "user:uuid:123" entityType: "user" version: 3 data: {name, email, role} 身份追踪 · 版本控制 · 生命周期 缓存值对象(Cache Value Object) 无唯一标识 data: {city: "北京", district: "海淀"} cachedAt: 2025-03-01T10:00:00Z 不可变 · 值相等性 · 共享安全 整体替换 · 无副作用 引用 缓存存储层(Redis / Local Cache) 实体 Key: user:{id} → 值对象 Key: address:{userId} 建模要点 实体通过 cacheId 追踪身份;值对象通过属性值判断相等性。 实体可引用多个值对象,值对象变更时整体替换而非原地修改。

3.6 实体与值对象的协作模式

在 OpenAPI 缓存治理中,实体与值对象通常以组合方式出现:

  1. 实体持有值对象:用户实体(Entity)持有地址值对象(Value Object)。地址变更时,直接替换整个值对象,用户实体的版本号递增。
  2. 值对象独立缓存:高频读取但极少变更的配置数据(如接口超时时间),直接作为值对象缓存,不绑定实体。
  3. 实体聚合值对象列表:订单实体包含商品列表(值对象集合),商品信息变更不影响订单实体的身份。
常见陷阱:不要将值对象“升级”为实体。例如,将地址值对象赋予唯一 ID 并追踪其变更,会导致缓存模型膨胀且一致性维护成本剧增。值对象的核心价值在于“无状态、可替换”。

3.7 建模后的治理收益

治理维度 建模前(混乱) 建模后(清晰)
缓存 Key 设计 随意拼接字符串,无规范 实体 Key 包含类型+ID,值对象 Key 包含上下文
失效策略 全局 TTL 或手动删除 实体按 ID 精确失效,值对象按 Key 整体失效
并发更新 直接覆盖,丢失更新 实体使用版本号乐观锁,值对象整体替换
一致性验证 难以追溯数据来源 实体可追踪变更历史,值对象可校验时间戳