I am working on creating a project using Clean Architecture (backend part). In this application I'm going to deal with users that can registrate/log in using jwt athentication and manipulate with another entity (let's say, with products). So, according to Clean Architecture principles I need to create 4 layers:
I started developing the project with Domain level:
This is the IRepository:
public interface IRepository<T> where T : Entity
{
//methods go here
}
This is the IUnitOfWork:
public interface IUnitOfWork {
IRepository<Product> ProductRepository { get; }
IRepository<User> UserRepository { get; } //User is a simple class not inheritated from IdentityUser (I can't use ApplicationUser in Domain)
public Task SaveAllAsync();
public Task DeleteDataBaseAsync();
public Task CreateDataBaseAsync();
}
My question is how to deal with both products and users if user's repository must use different dbcontext? For example, the AppDbContext for Product can look like that:
public class AppDbContext:DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options):base(options)
{
Database.EnsureCreated();
}
public DbSet<Product> Products{ get; set; }
}
But for users it will be inheritated from IdentityDbContext:
public class AppDbContext:IdentityDbContext<IdentityUser>
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<ApplicationUser> Users{ get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
So, here I deal with two different contexts whereas I need to use one in IRepository and IUnitOfWork implementation.
What am I supposed to do? Should I create two different repositories (like IUserRepository and IProductRepository) and create two separate classes to implement them? (like UserRepository which will have UserDbContext injected and ProductRepository with AppDbContext)
I tried to find related information on the Internet but there weren't any examples similar to my case. I've only seen one implementation when authentication logic was a part of UI layer and looked like a service, but this is not what I am looking for. I also found an interesting thought about adding ApplicationUser to domain project. This won't be correct because then that would mean adding a reference to "Microsoft.AspNetCore.Identity.EntityFrameworkCore" which goes against clean architecture.
As Panagiotis Kanavos highlighted, DbContext
is already a Unit of Work, and DbSet
is effectively a Repository. Creating separate abstractions often adds unnecessary complexity unless you need mocking or custom query logic. With a proper setup, you can focus on features rather than managing infrastructure complexity.
To address your concern, you don’t need multiple DbContext
instances. Instead, extend IdentityDbContext
to handle both identity and domain entities. This keeps identity concerns isolated while maintaining a single entry point for data access.
Here’s how you can do it:
public class ApplicationDbContext : IdentityDbContext<IdentityUser, IdentityRole, string>
{
public DbSet<Product> Products => Set<Product>();
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
}
full working example: Pragmatic Clean Architecture
This approach avoids the overhead of managing multiple contexts. For most CRUD operations, you can use DbContext directly. However, if needed, you can still encapsulate complex queries in a repository:
public class ProductRepository
{
private readonly ApplicationDbContext _dbContext;
public ProductRepository(ApplicationDbContext dbContext) => _dbContext = dbContext;
public async Task<Product?> GetByIdAsync(int id, CancellationToken cancellationToken) =>
await _dbContext.Products.FindAsync(new object[] { id }, cancellationToken);
}
full working example: Pragmatic Clean Architecture ProjectRepository.cs
In your case, however,if you don't want a decoupled domain User and instead, you want to use a customized ApplicationUser
, which inherits from IdentityUser
. To achieve this, you don't need to declare it separately as a DbSet
, as it is already defined in IdentityDbContext
when you pass it as a generic parameter.
Here is an example of this use case: https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Infrastructure/Data/ApplicationDbContext.cs
Access to these identity users is provided through IdentityServer. Typically, you would interact with them using the UserManager
and RoleManager
classes, as shown in the following example:
public class IdentityService(UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager)
: IIdentityService
{
public async Task<User> GetUserAsync(string id)
{
var identityUser = await userManager.FindByIdAsync(id)
?? throw new NotFoundException(nameof(User), id);
var roles = await userManager.GetRolesAsync(identityUser);
return new User(identityUser.Id, identityUser.UserName, new Email(identityUser.Email))
{
Roles = roles.Select(role => new Role(role, role, new List<string>())).ToList()
};
}
public async Task DeleteUserAsync(string userId) =>
await userManager.DeleteAsync(await userManager.FindByIdAsync(userId)
?? throw new NotFoundException(nameof(User), userId));
public async Task CreateRoleAsync(Role role) =>
await roleManager.CreateAsync(new IdentityRole { Name = role.Name });
}
full working example: Pragmatic Clean Architecture IdentityService.cs