0

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.

  • And actually It worked well cause I could create and save customer, shop and product before. After creating new module problem started to appear. Even if everything is registered the same way. – Mateusz Babski Jul 20 '23 at 15:57

0 Answers0