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 theIQueryableand the repository should not exposeIQueryableto the application.
- Do inherit the repository interface from IBasicRepository<TEntity, TKey>(as normally) or a lower-featured interface, likeIReadOnlyRepository<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 cancellationTokenparameter to every method of the repository. Example:
Task<IdentityUser> FindByNormalizedUserNameAsync(
    [NotNull] string normalizedUserName,
    CancellationToken cancellationToken = default
);
- Do add an optional bool includeDetails = trueparameter (default value istrue) 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 = falseparameter (default value isfalse) 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 includeDetailsoption 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.
 
 
                                             
                                    