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
- .NET 8 or superior
- A .NET project with these NuGet packages
- Paramore.Brighter.Outbox.Hosting - Provides the foundational infrastructure for implementing the outbox pattern in Brighter applications
- Paramore.Brighter.Outbox.MySql - Implements the outbox storage specifically for MySQL databases
- Paramore.Brighter.MessagingGateway.Kafka - Provides Kafka messaging capabilities
- Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection - Integrates Brighter with Microsoft's Dependency Injection framework
- Paramore.Brighter.ServiceActivator.Extensions.Hosting - Enables integration with Microsoft's Generic Host infrastructure
- Serilog.AspNetCore - Provides structured logging capabilities for ASP.NET Core applications
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; }
}
- 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>
{ ... }
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);
}
}
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);
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;
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);
...
})
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; })
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));
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");
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)