DEV Community

Cover image for Brighter: Migrating MySQL outbox to V10
Rafael Andrade
Rafael Andrade

Posted on

Brighter: Migrating MySQL outbox to V10

In the previous article, I discussed migrating the PostgreSQL outbox to Brighter V10. In this article, I'll focus specifically on the MySQL outbox migration process.

Requirement

Brighter Recap

Before continuing talk about MySQL outbox configuration, let's recap what we already know about Brighter.

Request (Command/Event)

Define messages using IRequest:

public class OrderPlaced() : Event(Id.Random())
{
    public string OrderId { get; set; } = string.Empty;
    public decimal Value { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
  • Commands: Single-recipient operations (e.g., SendEmail).
  • Events: Broadcast notifications (e.g., OrderShipped).

Message Mapper (Optional)

Translates between Brighter messages and your app objects, by default Brighter will use a JSON Serialzier

public class OrderPlacedMapper : IAmAMessageMapper<OrderPlaced>, IAmAMessageMapperAsync<OrderPlaced>
{ ... }
Enter fullscreen mode Exit fullscreen mode

Request Handler

Processes incoming messages:

public class OrderPlaceHandler(ILogger<OrderPlaceHandler> logger) : RequestHandler<OrderPlaced>
{
    public override Greeting Handle(Greeting command)
    {
        logger.LogInformation("{OrderId} placed with value {OrderValue}", command.OrderId, command.Value);
        return base.Handle(command);

    }
}
Enter fullscreen mode Exit fullscreen mode

Using the Outbox in Brighter

Before configuring the outbox, understand how to interact with it:

Publishing Messages Through the Outbox

Brighter provides the DepositPostAsync method to store messages in the outbox

await commandProcessor.DepositPostAsync(
    new OrderPlaced { OrderId = "ORD-123", Value = 99.99m }, 
    cancellationToken: cancellationToken);
Enter fullscreen mode Exit fullscreen mode

This stores the message in your MySQL outbox table for later processing.

Configuration package outbox

1. Ensure the table exists

First, create the outbox table in your MySQL database. Brighter won't manage this table for you—you're responsible for creating it and adding necessary indexes:

CREATE TABLE outboxmessages ( 
   `MessageId`VARCHAR(255) NOT NULL , 
   `Topic` VARCHAR(255) NOT NULL , 
   `MessageType` VARCHAR(32) NOT NULL , 
   `Timestamp` TIMESTAMP(3) NOT NULL , 
   `CorrelationId`VARCHAR(255) NULL ,
   `ReplyTo` VARCHAR(255) NULL ,
   `ContentType` VARCHAR(128) NULL , 
   `PartitionKey` VARCHAR(128) NULL , 
   `WorkflowId` VARCHAR(255) NULL ,
   `JobId` VARCHAR(255) NULL ,
   `Dispatched` TIMESTAMP(3) NULL , 
   `HeaderBag` TEXT NOT NULL , 
   `Body` TEXT NOT NULL , 
   `Source`  VARCHAR(255) NULL,
   `Type`  VARCHAR(255) NULL,
   `DataSchema`  VARCHAR(255) NULL,
   `Subject`  VARCHAR(255) NULL,
   `TraceParent`  VARCHAR(255) NULL,
   `TraceState`  VARCHAR(255) NULL,
   `Baggage`  TEXT NULL,
   `Created` TIMESTAMP(3) NOT NULL DEFAULT NOW(3),
   `CreatedID` INT(11) NOT NULL AUTO_INCREMENT, 
   UNIQUE(`CreatedID`),
   PRIMARY KEY (`MessageId`)
 ) ENGINE = InnoDB;
Enter fullscreen mode Exit fullscreen mode

2. Setup outbox

Set up the outbox in your dependency injection configuration:

 var conn = new RelationalDatabaseConfiguration(connectionString, "brightertests", "outboxmessages");

services
  .AddSingleton<IAmARelationalDatabaseConfiguration>(conn)
  .AddConsumers(opt => { ... }) // Or AddBrighter
  .AddProducers(opt => 
  {
     opt.ConnectionProvider = typeof(MySqlUnitOfWork);
     opt.TransactionProvider = typeof(MySqlUnitOfWork);
     opt.Outbox = new MySqlOutbox(outbox);
    ... 
  })
Enter fullscreen mode Exit fullscreen mode

3. Configure Sweeper

Set up the outbox in your dependency injection configuration:

services
  .AddSingleton<IAmARelationalDatabaseConfiguration>(conn)
  .AddConsumers(opt => { ... }) // Or AddBrighter
  .UseOutboxSweeper(opt => { opt.BatchSize = 10; })
Enter fullscreen mode Exit fullscreen mode

Note: If running in a multi-machine environment, ensure only one instance runs the sweeper or set distributed locking in Brighter to prevent duplicate message delivery.

4. Configure Outbox Archiving

After messages are successfully dispatched, you may want to archive them:

services
  .AddSingleton<IAmARelationalDatabaseConfiguration>(conn)
  .AddConsumers(opt => { ... }) // Or AddBrighter
  .UseOutboxArchiver<DbTransaction>(new NullOutboxArchiveProvider(),
                    opt => opt.MinimumAge = TimeSpan.FromMinutes(1));
Enter fullscreen mode Exit fullscreen mode

Key Differences Between Brighter V9 and V10

Outbox as Singleton Service

In Brighter V10, the outbox is implemented as a singleton service, which represents a significant architectural change:

  • V9: Outbox was scoped per request, allowing transaction sharing between application code and the outbox
  • V10: Outbox is a singleton, which improves performance but means you can no longer share transactions directly

If atomic message publishing across multiple messages is critical for your application, you'll need to implement a custom solution or consider opening a discussion on the Brighter GitHub repository.

Database Configuration Changes

In Brighter V10 outbox is a singleton, so now it's not possible to have a share transaction (maybe there is a workaround that I've found it), making not possible to have a atomic message publish, like if something fail don't sent any message. If you feel that is a new in your application, please open a discussation/isse on Brighter Github .

Database Configuration Changes

The RelationalDatabaseConfiguration constructor parameters have changed:

// V9
new RelationalDatabaseConfiguration(connectionString, "outboxmessages");

// V10
new RelationalDatabaseConfiguration(connectionString, "database_name", "outboxmessages");
Enter fullscreen mode Exit fullscreen mode

The second parameter now specifies the database name rather than the outbox table name, which supports improved telemetry capabilities.

Table Schema Changes

The V10 outbox table schema includes several new columns compared to V9:

  • TraceParent, TraceState, and Baggage columns for improved distributed tracing
  • Source, Type, DataSchema, and Subject columns for richer message metadata

Conclusion

Migrating your MySQL outbox to Brighter V10 requires attention to several key changes in the architecture and configuration. While the singleton outbox implementation improves performance, it does require adjusting your approach to transaction management. The enhanced schema provides better support for distributed tracing and richer message metadata, aligning Brighter more closely with modern observability practices.

Remember to:

  • Update your table schema to include the new columns Adjust your database configuration to specify both database name and table name
  • Reconsider your transaction strategy due to the singleton outbox implementation
  • Configure the sweeper and archiver with appropriate parameters for your environment

With these changes implemented, your application will be ready to take advantage of the improved performance and capabilities in Brighter V10 while maintaining reliable message delivery through the MySQL outbox pattern.

Top comments (0)