在仓储中返回聚合根是否合理?基于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
相关产品推荐
相关产品推荐

