Reputation: 53
I am trying to build a modular monolith application where each module is defined in its own class library project. I would like to utilize a single SQL Server 2019
database and create a separate db schema for each of the individual modules. I would like to also utilize the built-in MassTransit outbox pattern implementation. The thing is that I want to have the 3 auto-generated outbox tables in each database schema:
cards.InboxState
cards.OutboxState
cards.OutboxMessage
transactions.InboxState
transactions.OutboxState
transactions.OutboxMessage
This is my current MassTransit configuration:
builder.Services.AddMassTransit(x =>
{
var hostUri = builder.Configuration.GetValueOrThrow<string>("RABBITMQ_URI");
var username = builder.Configuration.GetValueOrThrow<string>("RABBITMQ_USERNAME");
var password = builder.Configuration.GetValueOrThrow<string>("RABBITMQ_PASSWORD");
x.AddConsumer<CreditCardCreatedByIssuerEventConsumer>();
x.AddConsumer<CreditCardRequestedEventConsumer>();
x.SetKebabCaseEndpointNameFormatter();
x.AddEntityFrameworkOutbox<CardsDbContext>(o =>
{
o.QueryDelay = TimeSpan.FromSeconds(3);
o.UseSqlServer();
o.UseBusOutbox();
});
x.AddEntityFrameworkOutbox<TransactionsDbContext>(o =>
{
o.QueryDelay = TimeSpan.FromSeconds(3);
o.UseSqlServer();
o.UseBusOutbox();
});
x.UsingRabbitMq((ctx, cfg) =>
{
cfg.Host(new Uri(hostUri), h =>
{
h.Username(username);
h.Password(password);
});
cfg.AutoStart = true;
cfg.ConfigureEndpoints(ctx);
});
});
I am currently getting the following error:
2024-03-15 18:59:17 fail: MassTransit.EntityFrameworkCoreIntegration.BusOutboxDeliveryService[0]
2024-03-15 18:59:17 ProcessMessageBatch faulted
2024-03-15 18:59:17 System.NullReferenceException: Object reference not set to an instance of an object.
2024-03-15 18:59:17 at MassTransit.Middleware.Outbox.BusOutboxNotification.WaitForDelivery(CancellationToken cancellationToken) in /_/src/MassTransit/Middleware/Outbox/BusOutboxNotification.cs:line 38
2024-03-15 18:59:17 at MassTransit.EntityFrameworkCoreIntegration.BusOutboxDeliveryService`1.ExecuteAsync(CancellationToken stoppingToken) in /_/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/EntityFrameworkCoreIntegration/BusOutboxDeliveryService.cs:line 72
The thing is that everything works as expected once I remove one of the db context registrations:
builder.Services.AddMassTransit(x =>
{
var hostUri = builder.Configuration.GetValueOrThrow<string>("RABBITMQ_URI");
var username = builder.Configuration.GetValueOrThrow<string>("RABBITMQ_USERNAME");
var password = builder.Configuration.GetValueOrThrow<string>("RABBITMQ_PASSWORD");
x.AddConsumer<CreditCardCreatedByIssuerEventConsumer>();
x.AddConsumer<CreditCardRequestedEventConsumer>();
x.SetKebabCaseEndpointNameFormatter();
x.AddEntityFrameworkOutbox<CardsDbContext>(o =>
{
o.QueryDelay = TimeSpan.FromSeconds(3);
o.UseSqlServer();
o.UseBusOutbox();
});
x.UsingRabbitMq((ctx, cfg) =>
{
cfg.Host(new Uri(hostUri), h =>
{
h.Username(username);
h.Password(password);
});
cfg.AutoStart = true;
cfg.ConfigureEndpoints(ctx);
});
});
So the logical question is if this is even possible?
And if it isn't could you suggest some sort of a workaround to this?
Any help would be greatly appreciated! Thanks!
Upvotes: 1
Views: 637
Reputation: 305
@Chris Patterson
At this point, I am not thinking of anything elegant. But what if there were a method that allowed us to pass the correct DbContext to the utilized endpoint? Wouldn't that work?
I noticed that currently, the Outbox doesn't record entries in the table because the instantiated DbContext is incorrect. MassTransit only uses the last configured DbContext, and therefore, SaveChanges() will not save updates from another context.
Example:
x.AddEntityFrameworkOutbox<ReportingDbContext>(o =>
{
o.UseSqlServer(false);
o.UseBusOutbox();
});
x.AddEntityFrameworkOutbox<OrderingDbContext>(o =>
{
o.UseSqlServer(false);
o.UseBusOutbox();
});
In this case, only the OrderingDbContext is within the MassTransit context because it was the last one configured.
So if I am working in the Reporting context, the Outbox tables are not recorded by EF because MassTransit is using another DbContext.
However, if I make changes in the Ordering context, since I am using the same DbContext that MassTransit is using (it is the same instance / scoped), SaveChanges() will work, and the Outbox will function correctly, recording the event and subsequently calling the consumers.
Wouldn't passing the correct DbContext to MassTransit resolve this issue and allow us to work with multiple DbContexts?
I always code with multiple DbContexts, one for each ClassLibrary within the same Host (when modular monolith).
It would be excellent to use MassTransit with multiple DbContexts.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Ensure a different Schema for each DbContext.
modelBuilder.HasDefaultSchema(Constants.Database.Schema);
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
base.OnModelCreating(modelBuilder);
modelBuilder.AddInboxStateEntity();
modelBuilder.AddOutboxMessageEntity();
modelBuilder.AddOutboxStateEntity();
}
Upvotes: 2
Reputation: 33457
No, it isn't. The transactional outbox only works with the default bus on a single DbContext
.
Upvotes: 2