.NET Core 6微服务整洁架构下泛型仓储使用疑问
.NET Core 整洁架构下仓储模式实践建议
首先纠正一个认知偏差:不存在“泛型仓储过时”的说法,被业内淘汰的是无意义的薄包装泛型仓储,不是泛型仓储本身。你现在遇到的仓储数量爆炸问题,本质是仓储粒度设计完全错了,和用不用泛型没有直接关系。
你当前设计的核心问题
你现在给每个单表、甚至单个业务操作单独建仓储(比如IStoreCarPricesRepository、IStoreCarSparePartRepository),完全违背了仓储模式的设计初衷:
仓储的核心作用是封装聚合根的持久化边界,对上层屏蔽数据存储的实现细节,不是给每个数据库操作、每张单独的表套一层接口。
按你现在的写法,哪怕不上泛型,后续仓储数量也会随着表和业务操作的增加无限膨胀,平白增加大量重复代码和维护成本。
为什么有人说泛型仓储“过时”
被吐槽的是那种只有GetById、Add、Update、Delete、GetAll五个方法的公开泛型仓储IRepository<T>:
- 这种实现本质是对EF Core
DbSet<T>的一层无意义薄包装,没有提供任何额外价值,平白多了一层抽象 - 绝大多数这类实现最后都会忍不住加
IQueryable<T> Query()方法,直接把查询逻辑泄露到应用层,仓储彻底变成泄漏抽象,完全失去了隔离持久化细节的作用
适配你当前架构的落地方案
结合.NET 6微服务+整洁架构的场景,推荐用「通用泛型基类+聚合专属仓储」的组合方案,既不会出现仓储数量爆炸,也不会出现薄包装无意义抽象的问题:
- 第一步:重新划仓储边界,只给聚合根定义仓储
先梳理Core层的业务聚合,仓储接口只和聚合根一一对应,不要给聚合内部的实体、值对象单独建仓储。比如Car是聚合根,CarPrice、CarSparePart是聚合内部的关联实体,所有和车价、备件相关的持久化操作,全部收敛到ICarRepository接口中,不需要单独建对应仓储。最终仓储的数量和系统聚合根数量对齐,不会无限增长。 - 第二步:分层实现泛型能力,不对外暴露无意义的公开泛型接口
- 在Core层定义最小通用仓储接口
IRepository<T> where T : IAggregateRoot,只放所有聚合都支持的、不会泄漏持久化细节的方法,比如Task<T> GetByIdAsync(Guid id)、Task AddAsync(T entity)、Task SaveChangesAsync(),绝对不要加返回IQueryable的方法。 - 在Infrastructure层实现内部泛型基类
RepositoryBase<T> where T : class, IAggregateRoot,把所有聚合通用的持久化逻辑(比如增删改查、软删除、事务处理)封装在这里,这个基类不需要作为公共接口对外暴露,只给Infrastructure层内部的具体仓储实现做复用。 - 针对每个聚合根,在Core层定义继承自通用接口的专属仓储接口,比如
ICarRepository : IRepository<Car>,把这个聚合特有的持久化操作(比如批量更新车价、批量导入备件、按车型查询关联备件)定义在这个专属接口里;在Infrastructure层实现CarRepository : RepositoryBase<Car>, ICarRepository,复用基类的通用逻辑,同时实现聚合特有的持久化方法。
- 在Core层定义最小通用仓储接口
- 第三步:特殊场景简化处理
你提到的「从不同队列接收消息存入对应数据表」这类没有复杂聚合逻辑、纯写入的场景,不需要给每个表单独建仓储:如果是非业务类的事件、日志类数据,可以统一抽一个通用的IMessageLogRepository处理;如果是业务聚合相关的消息,直接调用对应聚合根的仓储方法完成写入即可。
额外注意事项
- 不要为了套模式硬加抽象:如果你的微服务是非常简单的CRUD服务,没有复杂的领域逻辑,甚至可以直接用EF Core的DbContext做持久化,不需要硬套仓储层。
- 仓储里不要放业务逻辑:仓储只负责持久化和数据查询,业务判断、流程编排全部放到Core层的用例里。
内容的提问来源于stack exchange,提问作者csharper
相关产品推荐
相关产品推荐

