You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

聚合根类中的Version属性是否泄露事件溯源实现细节?

这确实属于基础设施细节泄露,解决方案看这里

你观察得很准——version属性完全是事件溯源存储和并发控制的基础设施需求,和领域核心逻辑没有关系,把它放在领域层的聚合根基类里,确实违反了清洁架构中“领域层独立于基础设施”的原则,属于细节泄露。

下面是几个可行的解决思路:

1. 将版本信息与聚合根分离,由基础设施层维护

在基础设施层(比如事件存储仓库)中单独维护聚合根的版本映射,领域层的聚合根完全不知道version的存在。举个JS的例子:

// 领域层 - 纯领域聚合根
abstract class Aggregate {
  public ID: string;

  // 只包含领域相关的方法和属性
  applyEvent(event: DomainEvent): void {
    // 领域逻辑处理事件
  }
}

// 基础设施层 - 事件存储仓库
class EventStoreRepository {
  private aggregateVersions: Map<string, string> = new Map();

  async save(aggregate: Aggregate, newEvents: DomainEvent[]): Promise<void> {
    const currentVersion = this.aggregateVersions.get(aggregate.ID) || "0";
    // 基于currentVersion做并发校验(比如乐观锁)
    // 保存事件到存储
    // 更新版本为新的版本号
    this.aggregateVersions.set(aggregate.ID, calculateNewVersion(currentVersion));
  }

  async load<T extends Aggregate>(aggregateType: new () => T, id: string): Promise<T> {
    const aggregate = new aggregateType();
    aggregate.ID = id;
    const events = await this.loadEvents(id);
    // 应用事件重建聚合根状态
    events.forEach(event => aggregate.applyEvent(event));
    // 加载对应的版本,但不传递给聚合根本身
    this.aggregateVersions.set(id, getLastEventVersion(events));
    return aggregate;
  }
}

2. 用领域事件的元数据承载版本信息

把版本信息作为每个领域事件的元数据(而非聚合根的属性),当加载聚合根时,通过事件序列的元数据计算当前版本;保存时,基于最后一个事件的版本做并发控制。这样聚合根全程不需要接触版本属性:

// 领域层 - 纯领域事件,不带版本
interface DomainEvent {
  aggregateId: string;
  eventType: string;
  // 领域事件数据
}

// 基础设施层 - 带元数据的存储事件
interface StoredEvent extends DomainEvent {
  version: string;
  timestamp: Date;
}

// 基础设施层 - 仓库实现
class EventStoreRepository {
  async save(aggregate: Aggregate, newEvents: DomainEvent[]): Promise<void> {
    const lastEvent = await this.getLastEvent(aggregate.ID);
    const nextVersion = lastEvent ? incrementVersion(lastEvent.version) : "1";
    // 给新事件添加版本元数据,然后保存
    const storedEvents = newEvents.map((event, index) => ({
      ...event,
      version: incrementVersion(nextVersion, index)
    }));
    // 保存到存储,并做并发校验
  }
}

3. 用接口分离隔离版本属性

在领域层定义只包含领域逻辑的聚合根接口,基础设施层实现该接口时再加入版本属性。领域层依赖接口而非具体实现,从而隔离基础设施细节:

// 领域层 - 聚合根接口
interface DomainAggregate {
  ID: string;
  applyEvent(event: DomainEvent): void;
  // 其他领域方法
}

// 领域层 - 抽象实现(可选)
abstract class BaseAggregate implements DomainAggregate {
  public ID: string;

  applyEvent(event: DomainEvent): void {
    // 通用事件处理逻辑
  }
}

// 基础设施层 - 带版本的具体实现
class EventSourcedAggregate extends BaseAggregate {
  public version: string; // 仅在基础设施层使用

  // 实现领域方法
}

很多示例把version放在聚合根基类里,只是为了快速实现,忽略了架构边界。如果要严格遵循清洁架构,必须把这类基础设施细节从领域层剥离出去,保持领域层的纯粹性。

内容的提问来源于stack exchange,提问作者alaboudi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 23:34:55