DDD架构下应用层仓储接口可否复用基础设施层基类实现
结论
结合你给出的项目背景,你的方案合理性远高于同事的方案,理由如下:
对同事两个顾虑的回应
- 关于「AddAsync命名过于泛化、无法判断添加实体类型」的问题
你们项目本身遵循SOLID原则,单处理器仅处理单一聚合根,且命名规范清晰,实际调用代码一定是IOrderRepository orderRepository注入后调用orderRepository.AddAsync(order),不管是仓储变量名、还是入参的Order类型,都能100%明确操作的实体类型,不存在歧义。反而orderRepository.AddOrderAsync(order)属于冗余命名,重复的语义信息反而不符合干净代码的命名规范。 - 关于「不应当在接口中暴露基类方法、基类方法仅应在继承层级暴露」的问题
这是对基类职责的误解:你们的BaseRepository<T,U>本质是通用仓储实现的抽离层,核心作用就是提供可复用的公共CRUD实现给子类仓储使用,而非封装仅内部可见的私有逻辑。仓储接口定义的是对外的业务契约,只要该方法是契约要求的能力,实现来源是基类还是子类本身并不影响合理性。同事的方案等于人为限制了基类的复用价值,强制每个子类编写重复的转发逻辑,反而增加了复制粘贴引发的bug风险,比如示例中每个仓储都要单独写日志捕获逻辑,漏写、错写的概率会大幅提升。
你的方案的核心优势
- 大幅减少样板代码:无需每个仓储重复编写
AddXXXAsync转发方法,示例中的异常日志捕获逻辑也可以统一抽离到基类AddAsync中,后续要调整日志规则仅需修改基类一处即可,维护成本大幅降低。 - 完全保留扩展性:基类方法声明为
public virtual,如果后续某个聚合的添加逻辑需要特殊处理(比如新增前置校验、触发领域事件等),直接在对应子类仓储中重写AddAsync方法即可,符合开闭原则。 - 符合行业通用实践:.NET生态中主流的DDD框架(如ABP等)的仓储实现都采用该模式,通过通用仓储接口定义标准CRUD方法,业务聚合仓储继承通用接口,兼顾复用性和灵活性。
折中方案(如果同事仍有顾虑)
可以额外定义泛型通用仓储接口:
public interface IRepository<T> where T : class { IUnitOfWork UnitOfWork { get; } Task AddAsync(T entity); // 其他通用CRUD方法 }
业务聚合的仓储接口直接继承该通用接口即可:
public interface IOrderRepository : IRepository<Order> { // 仅需声明订单独有的仓储方法即可 }
既不需要每个接口重复声明通用方法,也符合接口隔离原则,不需要通用CRUD能力的聚合可以不继承该通用接口。
内容的提问来源于stack exchange,提问作者RealYlem
相关产品推荐
相关产品推荐

