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

在仓储中返回聚合根是否合理?基于Clean Architecture的实现疑问

Clean Architecture 实现疑问解答

背景与现有实现

我正在学习Clean Architecture,MyEntity继承自AggregateRoot。每次从数据库查询记录时,我会调用仓储方法查询,再将结果转换为MyEntity并为其应用事件。现有代码如下:

应用层用例

@Injectable()
export class MyUseCases {
  constructor(private myFactory: MyFactory) {}

  async myMethod(id: number) {
    const result = await this.myFactory.findById(data);
    // 执行业务逻辑
  }
}

工厂实现

@Injectable()
export class MyFactory implements IEntityFactory<MyEntity> {
  constructor(private readonly myRepository: MyRepository) {}
  async findOneById(id: number): Promise<MyEntity> {
    const schemaRecord = await this.myRepository.findById(id);
    const myEntity = new MyEntity(schemaRecord.toJSON());
    // myEntity.apply(); 
    return myEntity;
  }
}

当前架构分层:

  • 基础设施层:数据库、MyRepository、Schema
  • 应用层:MyUseCase、MyFactory端口
  • 领域层:MyEntity模型

我遵循了Clean Architecture“下层模块不得依赖上层模块”的原则,用Factory作为端口转换聚合根实例,而非在仓储中直接转换ORM结果,请问这种实现是否有误?


核心思路没问题,但存在职责划分与端口归属的优化点

你的核心方向是对的——通过依赖倒置避免领域层依赖基础设施层,但在几个细节上可以调整,让架构更贴合Clean Architecture的设计原则:

1. 工厂职责越界:不应承担数据查询逻辑

工厂的核心职责是创建/重建聚合根(包括从原始数据恢复聚合状态、应用领域事件),而数据查询是仓储的专属职责。现有代码让工厂依赖仓储并执行查询,违背了单一职责原则,也让工厂耦合了数据访问逻辑。

2. 端口归属错误:IEntityFactory应属于领域层

IEntityFactory是创建领域模型的抽象,属于领域层的核心抽象之一,而非应用层。领域层负责定义自身模型的创建规则,应用层仅依赖这些抽象即可。

3. 仓储的抽象缺失:仓储接口(端口)应放在领域层

按照Clean Architecture的规范,仓储的抽象接口(端口)应该由领域层定义,基础设施层实现具体的ORM仓储。这样领域层可以独立定义数据访问的需求,完全不受具体数据库/ORM的影响。


调整后的参考实现

领域层

// 领域模型:MyEntity(继承AggregateRoot)
export class MyEntity extends AggregateRoot {
  constructor(rawData: any) {
    super();
    // 初始化实体属性
  }

  apply() {
    // 应用事件恢复聚合状态
  }
}

// 领域层 - 仓储端口:定义数据访问需求
export interface IMyRepository {
  findById(id: number): Promise<any>; // 返回原始数据结构,不涉及领域模型转换
}

// 领域层 - 工厂端口:定义聚合根创建规则
export interface IEntityFactory<T> {
  createFromRawData(rawData: any): T;
}

应用层

@Injectable()
export class MyUseCases {
  constructor(
    private readonly myRepository: IMyRepository,
    private readonly myFactory: IEntityFactory<MyEntity>
  ) {}

  async myMethod(id: number) {
    // 仓储负责查询原始数据
    const rawData = await this.myRepository.findById(id);
    // 工厂负责将原始数据转换为聚合根
    const myEntity = this.myFactory.createFromRawData(rawData);
    // 执行业务逻辑
  }
}

基础设施层

// 仓储实现:实现领域层的IMyRepository端口
@Injectable()
export class MyRepository implements IMyRepository {
  constructor(private readonly model: MySchemaModel) {}

  async findById(id: number): Promise<any> {
    const record = await this.model.findById(id);
    return record?.toJSON() ?? null;
  }
}

// 工厂实现:实现领域层的IEntityFactory端口
@Injectable()
export class MyFactory implements IEntityFactory<MyEntity> {
  createFromRawData(rawData: any): MyEntity {
    const myEntity = new MyEntity(rawData);
    myEntity.apply(); // 应用事件恢复聚合状态
    return myEntity;
  }
}

总结

你的核心设计思路符合Clean Architecture的依赖倒置原则,通过调整职责划分和端口归属,可以让各层的边界更清晰:

  • 领域层完全独立,定义自身的模型、仓储抽象和工厂抽象
  • 应用层仅依赖领域层的抽象,协调仓储和工厂完成业务逻辑
  • 基础设施层专注于实现具体的数据访问和模型转换细节

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 23:20:29