I am working on project built around modular monolith. I have separated DbContexts into modules, UnitOfWork pattern as well (hopefully). I have a problem with saving to database. System works only on last registered DbContext and doesnt see the others. I can create a customer entity but it doesnt save to db. When I am logged in to some customer I can create a basket and add products to it (cause last registered DbContext is BasketsDbContext).
I provide code - it looks the same for all modules so I paste it only once.
Shared.Infrastructure.SqlServerUnitOfWork
public abstract class SqlServerUnitOfWork<T> : IUnitOfWork where T : DbContext
{
private readonly T _dbContext;
private readonly IDomainEventDispatcher _domainEventDispatcher;
protected SqlServerUnitOfWork(T dbContext, IDomainEventDispatcher domainEventDispatcher)
{
_dbContext = dbContext;
_domainEventDispatcher = domainEventDispatcher;
}
public async Task CommitAndDispatchDomainEventsAsync<TEntity>(TEntity entity) where TEntity : Entity
{
Log.Information("dbcontext: {@a}", _dbContext.GetType().Name);
await _domainEventDispatcher.DispatchDomainEvents(entity);
await _dbContext.SaveChangesAsync();
}
public async Task<int> CommitChangesAsync()
{
return await _dbContext.SaveChangesAsync();
}
}
Shared.Abstractions.UnitOfWork
public interface IUnitOfWork
{
Task<int> CommitChangesAsync();
Task CommitAndDispatchDomainEventsAsync<TEntity>(TEntity entity)
where TEntity : Entity;
}
Shared.Infrastructure.Extensions
public static IServiceCollection AddUnitOfWork<T>(this IServiceCollection services) where T : class, IUnitOfWork
{
services.AddScoped<IUnitOfWork, T>();
services.AddScoped<T>();
using var serviceProvider = services.BuildServiceProvider();
serviceProvider.GetRequiredService<UnitOfWorkTypeRegistry>().Register<T>();
return services;
}
Module UoW registering
public static IServiceCollection AddCustomersInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<CustomersDbContext>(options =>
{
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
});
services.AddScoped<ICustomerRepository, CustomerRepository>();
services.AddUnitOfWork<CustomersUnitOfWork>();
return services;
}
CustomersUnitOfWork
internal class CustomersUnitOfWork : SqlServerUnitOfWork<CustomersDbContext>
{
public CustomersUnitOfWork(CustomersDbContext dbContext,
IDomainEventDispatcher domainEventDispatcher)
: base(dbContext, domainEventDispatcher)
{
}
}
Into handlers I inject IUnitOfWork and trying to play with it i publish and handle events with ease but it doesnt save anything thats is not BasketsDbContext (last registered one)
SignUpCustomerCommandHandler
public async Task<AuthenticationResult> Handle(SignUpCustomerCommand command, CancellationToken cancellationToken)
{
var emailCheck = await _customerRepository.GetCustomerByEmail(command.Email);
if (emailCheck != null)
{
throw new BadRequestException("Email in use");
}
var validator = new SignUpCustomerValidator();
validator.ValidateAndThrow(command);
//await CheckIfEmailIsFreeToUse(command.Email);
var passwordHash = _hashingService.GenerateHashPassword(command.Password);
var address = Address.CreateAddress(command.Country, command.City, command.Street, command.PostalCode);
var customer = Customer.Create(command.Email,
passwordHash,
command.Name,
command.LastName,
address,
command.TelephoneNumber);
await _customerRepository.Add(customer);
await _unitOfWork.CommitAndDispatchDomainEventsAsync(customer);
var token = _tokenManager.GenerateToken(customer.Id, customer.Email, customer.Role);
return new AuthenticationResult(customer.Id, token);
}
Log
[16:26:37 INF] Starting request: SignUpCustomerCommand, 07/20/2023 16:26:37 +02:00
[16:26:39 INF] dbcontext: BasketsDbContext
[16:26:39 INF] Domain event: {"Customer": {"Id": {"Value": "847e33eb-08e6-440b-94eb-cc3786a56944", "$type": "CustomerId"}, "Email": {"Value": "test@example.com", "$type": "Email"}, "PasswordHash": {"Value": "$2a$08$fiOMM8UZXcg/m1IF6afVvuhU37BghzvfseTYH0.r8jXQE.heAqTOS", "$type": "PasswordHash"}, "Name": {"Value": "string", "$type": "Name"}, "LastName": {"Value": "string", "$type": "LastName"}, "Address": {"Country": "string", "City": "string", "Street": "string", "PostalCode": "string", "$type": "Address"}, "TelephoneNumber": {"Value": "1", "$type": "TelephoneNumber"}, "Role": "customer", "DomainEvents": [], "$type": "Customer"}, "$type": "CustomerCreatedDomainEvent"}
[16:26:39 INF] Customer created at: 07/20/2023 14:26:39
[16:26:39 INF] Customer created at: 07/20/2023 16:26:39 +02:00
[16:26:40 WRN] Long running request: SignUpCustomerCommand, (2588 milliseconds) - 07/20/2023
16:26:40 +02:00, {"Email": "test@example.com", "Password": "string", "Name": "string",
"LastName": "string", "TelephoneNumber": "1", "Country": "string", "City": "string", "Street":
"string", "PostalCode": "string", "$type": "SignUpCustomerCommand"}
[16:26:40 INF] Completed request: SignUpCustomerCommand, 07/20/2023 16:26:40 +02:00
[16:26:40 INF] HTTP POST /api/Customers/SignUp responded 200 in 2765.2058 ms
I can of course write in handler expliciltly
_domainEventDispatcher.DispatchEvents(customer);
_customerRepository.Commit();
And it works well but with complex handler with some events it can be painful.