Versão
Idioma

Repository Best Practices & Conventions

Repository Interfaces

  • Do define repository interfaces in the domain layer.
  • Do define a repository interface (like IIdentityUserRepository) and create its corresponding implementations for each aggregate root.
    • Do always use the created repository interface from the application code.
    • Do not use generic repository interfaces (like IRepository<IdentityUser, Guid>) from the application code.
    • Do not use IQueryable<TEntity> features in the application code (domain, application... layers).

For the example aggregate root:

public class IdentityUser : AggregateRoot<Guid>
{
    //...
}

Define the repository interface as below:

public interface IIdentityUserRepository : IBasicRepository<IdentityUser, Guid>
{
    //...
}
  • Do not inherit the repository interface from the IRepository<TEntity, TKey> interface. Because it inherits the IQueryable and the repository should not expose IQueryable to the application.
  • Do inherit the repository interface from IBasicRepository<TEntity, TKey> (as normally) or a lower-featured interface, like IReadOnlyRepository<TEntity, TKey> (if it's needed).
  • Do not define repositories for entities those are not aggregate roots.

Repository Methods

  • Do define all repository methods as asynchronous.
  • Do add an optional cancellationToken parameter to every method of the repository. Example:
Task<IdentityUser> FindByNormalizedUserNameAsync(
    [NotNull] string normalizedUserName,
    CancellationToken cancellationToken = default
);
  • Do create a synchronous extension method for each asynchronous repository method. Example:
public static class IdentityUserRepositoryExtensions
{
    public static IdentityUser FindByNormalizedUserName(
        this IIdentityUserRepository repository,
        [NotNull] string normalizedUserName)
    {
        return AsyncHelper.RunSync(
            () => repository.FindByNormalizedUserNameAsync(normalizedUserName)
        );
    }
}

This will allow synchronous code to use the repository methods easier.

  • Do add an optional bool includeDetails = true parameter (default value is true) for every repository method which returns a single entity. Example:
Task<IdentityUser> FindByNormalizedUserNameAsync(
    [NotNull] string normalizedUserName,
    bool includeDetails = true,
    CancellationToken cancellationToken = default
);

This parameter will be implemented for ORMs to eager load sub collections of the entity.

  • Do add an optional bool includeDetails = false parameter (default value is false) for every repository method which returns a list of entities. Example:
Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
    string normalizedRoleName, 
    bool includeDetails = false,
    CancellationToken cancellationToken = default
);
  • Do not create composite classes to combine entities to get from repository with a single method call. Examples: UserWithRoles, UserWithTokens, UserWithRolesAndTokens. Instead, properly use includeDetails option to add all details of the entity when needed.
  • Avoid to create projection classes for entities to get less property of an entity from the repository. Example: Avoid to create BasicUserView class to select a few properties needed for the use case needs. Instead, directly use the aggregate root class. However, there may be some exceptions for this rule, where:
    • Performance is so critical for the use case and getting the whole aggregate root highly impacts the performance.

See Also

Esta página foi útil?
Por favor, faça uma seleção.
Obrigado pelo seu valioso feedback!

Observe que, embora não possamos responder aos comentários, nossa equipe usará seus comentários para melhorar a experiência.

Neste documento
Mastering ABP Framework Book
Dominando a estrutura ABP

Este livro o ajudará a obter uma compreensão completa da estrutura e das técnicas modernas de desenvolvimento de aplicativos da web.