dbcontextmasstransitoutbox-patterntransactional-outbox

Is it possible to use MassTransit transactional outbox with multiple db contexts?


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!


Solution

  • No, it isn't. The transactional outbox only works with the default bus on a single DbContext.