仓储最佳实践与规范
本文档基于领域驱动设计原则,为模块和应用程序中实现仓储类提供最佳实践。
请确保已先阅读 仓储 文档 。
仓储接口
- 务必 在 领域层 定义仓储接口。
- 务必 为 每个聚合根 定义仓储接口(如
IIdentityUserRepository)并创建其对应的实现。- 务必 始终在应用代码中使用已创建的仓储接口。
- 请勿 在应用代码中使用泛型仓储接口(如
IRepository<IdentityUser, Guid>)。 - 请勿 在应用代码(领域层、应用层等)中使用
IQueryable<TEntity>功能。
对于示例聚合根:
public class IdentityUser : AggregateRoot<Guid>
{
//...
}
按如下方式定义仓储接口:
public interface IIdentityUserRepository : IBasicRepository<IdentityUser, Guid>
{
//...
}
- 请勿 让仓储接口继承自
IRepository<TEntity, TKey>接口。因为它继承了IQueryable,而仓储不应向应用暴露IQueryable。 - 务必 让仓储接口继承自
IBasicRepository<TEntity, TKey>(通常如此)或功能较少的接口,如IReadOnlyRepository<TEntity, TKey>(如果需要)。 - 请勿 为 非聚合根 的实体定义仓储。
仓储方法
- 务必 将所有仓储方法定义为 异步 方法。
- 务必 为仓储的每个方法添加一个 可选 的
cancellationToken参数。示例:
Task<IdentityUser> FindByNormalizedUserNameAsync(
[NotNull] string normalizedUserName,
CancellationToken cancellationToken = default
);
- 务必 为每个返回 单个实体 的仓储方法添加一个可选的
bool includeDetails = true参数(默认值为true)。示例:
Task<IdentityUser> FindByNormalizedUserNameAsync(
[NotNull] string normalizedUserName,
bool includeDetails = true,
CancellationToken cancellationToken = default
);
该参数将用于 ORM 实现以预加载实体的子集合。
- 务必 为每个返回 实体列表 的仓储方法添加一个可选的
bool includeDetails = false参数(默认值为false)。示例:
Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
string normalizedRoleName,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
- 请勿 创建复合类来组合实体,以便通过单个方法调用从仓储获取。例如:UserWithRoles、UserWithTokens、UserWithRolesAndTokens。相反,应适当使用
includeDetails选项在需要时添加实体的所有详细信息。 - 避免 为实体创建投影类以从仓储获取较少属性。例如:避免创建 BasicUserView 类来选择用例所需的少量属性。相反,应直接使用聚合根类。然而,此规则可能存在一些例外情况,例如:
- 用例对性能要求极高,且获取整个聚合根严重影响性能。
抠丁客


