项目

仓储最佳实践与规范

本文档基于领域驱动设计原则,为模块和应用程序中实现仓储类提供最佳实践。

请确保已先阅读 仓储 文档 。

仓储接口

  • 务必领域层 定义仓储接口。
  • 务必每个聚合根 定义仓储接口(如 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
);
  • 请勿 创建复合类来组合实体,以便通过单个方法调用从仓储获取。例如:UserWithRolesUserWithTokensUserWithRolesAndTokens。相反,应适当使用 includeDetails 选项在需要时添加实体的所有详细信息。
  • 避免 为实体创建投影类以从仓储获取较少属性。例如:避免创建 BasicUserView 类来选择用例所需的少量属性。相反,应直接使用聚合根类。然而,此规则可能存在一些例外情况,例如:
    • 用例对性能要求极高,且获取整个聚合根严重影响性能。

另请参阅

在本文档中