In my previous article, I discussed Brighter V10 and its new features. This follow-up focuses on the essential migration path from V9 to V10, highlighting breaking changes and providing updated code examples.
Dependency Injection Registration
One of the first and most significant changes you'll encounter is how Brighter is registered with the Microsoft DI container. The AddServiceActivator method is gone, replaced by more specific methods for consumers and producers.
Messaging Consumer and Producer Setup
In V9, you typically used AddServiceActivator for consumers. In V10, this registration is now explicitly split into AddConsumers and AddProducers, giving you more granular control.
Notice also that opt.ChannelFactory has been renamed to opt.DefaultChannelFactory.
V9:
services
.AddServiceActivator(opt =>
{
opt.Subscriptions = [...];
opt.ChannelFactory = new ChannelFactory(...);
})
.UseExternalBus(...)
V10:
services
.AddConsumers(opt =>
{
opt.Subscriptions = [...];
opt.DefaultChannelFactory = new ChannelFactory(...);
})
.AddProducers(opt =>
{
opt.ProducerRegistry = ....
});
Inbox and Outbox Configuration
Previously, you configured the Inbox and Outbox by chaining methods like UseInMemoryInbox() after AddServiceActivator. In V10, this configuration is moved directly into the AddConsumers (for the Inbox) and AddProducers (for the Outbox) options.
When configuring an Outbox in V10, you must also now explicitly configure the ConnectionProvider and TransactionProvider.
V9:
services
.AddServiceActivator(opt =>
{
opt.Subscriptions = [...];
opt.ChannelFactory = new ChannelFactory(...);
})
.UseInMemoryInbox()
.UseInMemoryOutbox()
.UseExternalBus(...)
V10:
services
.AddConsumers(opt =>
{
opt.Subscriptions = [...];
opt.InboxConfiguration = new InboxConfiguration(...);
opt.DefaultChannelFactory = new ChannelFactory(...);
})
.AddProducers(opt =>
{
opt.ProducerRegistry = ....;
opt.Outbox = ...;
opt.ConnectionProvider = typeof(...);
opt.TransactionProvider = typeof(...);
});
Component Lifetime
With V10, it is strongly recommended to register your Message Mappers, Transformers, and Request Handlers as Scoped or Transient.
This is because these components now often include a RequestContext property. This property is updated by the framework at runtime (before methods are called), and using a Singleton lifetime could lead to concurrency issues and stale data.
Message Mapper Interface
The IAmAMessageMapper interface has two key breaking changes.
-
MapToMessageSignature: The method now includes aPublicationparameter:MapToMessage(Greeting request, Publication publication). This is required to support new default mapper functionality. -
RequestContextProperty: The interface now requires apublic IRequestContext? Context { get; set; }property. This allows the framework or the requester to pass additional context and data at runtime.
V9
public class GreetingMapper : IAmAMessageMapper<Greeting>
{
public Message MapToMessage(Greeting request)
{
var header = new MessageHeader();
header.Id = request.Id;
header.TimeStamp = DateTime.UtcNow;
header.Topic = "greeting.topic";
header.MessageType = MessageType.MT_EVENT;
var body = new MessageBody(JsonSerializer.Serialize(request));
return new Message(header, body);
}
public Greeting MapToRequest(Message message)
{
return JsonSerializer.Deserialize<Greeting>(message.Body.Bytes)!;
}
}
V10
public class GreetingMapper : IAmAMessageMapper<Greeting>
{
public IRequestContext? Context { get; set; }
public Message MapToMessage(Greeting request, Publication publication)
{
var header = new MessageHeader();
header.Id = request.Id;
header.TimeStamp = DateTime.UtcNow;
header.Topic = "greeting.topic";
header.MessageType = MessageType.MT_EVENT;
var body = new MessageBody(JsonSerializer.Serialize(request));
return new Message(header, body);
}
public Greeting MapToRequest(Message message)
{
return JsonSerializer.Deserialize<Greeting>(message.Body.Bytes)!;
}
}
Async Mappers
A subtle but important related change: If you are using RequestHandlerAsync (for asynchronous handlers), you must now implement the corresponding IAmAMessageMapperAsync interface instead of the synchronous IAmAMessageMapper.
Outbox Breaking Changes
If you are using the Outbox from Brighter V10, especially with a relational database (like PostgreSQL, MySQL, or Microsoft SQL Server), there are several breaking schema changes.
You must add the following columns to your Inbox/Outbox tables to support enhanced context and tracing:
- Source: VARCHAR(255)
- DataSchema: VARCHAR(255)
- Subject: VARCHAR(255)
- TraceParent: VARCHAR(255)
- TraceState: VARCHAR(255)
- Baggage: TEXT (or VARCHAR(MAX) in SQL Server)
MessageId Column Changes
The MessageId column type has been updated in several implementations.
-
PostgreSQL: The
MessageIdtype must be changed fromUUIDtoVARCHAR(255).
Optional (Recommended) Changes: For better performance and native type usage, you can make the following platform-specific changes:
-
MySQL: Change
MessageIdfromCHAR(36)toVARCHAR(255). -
Microsoft SQL Server: Change
MessageIdfromUNIQUEIDENTIFIERtoVARCHAR(255).
Migration Strategy and Best Practices
When migrating from V9 to V10, consider this approach:
- Start with Registration: Update your DI configuration first, as this affects everything else
- Configure Inbox/Outbox: Move your inbox/outbox configuration to the appropriate registration blocks
- Update Mappers: Refactor all message mappers to implement the new interface requirements
- Review Lifetimes: Audit all Brighter-related components and ensure they're registered as Scoped or Transient
- Test Thoroughly: Pay special attention to transaction management and async operations
Conclusion
Migrating to Brighter V10 involves several key changes, primarily focused on clearer DI registration, explicit Inbox/Outbox setup, and enhanced message mapping with request context. By updating your service registration and mapper implementations as shown above, you can smoothly transition your application to the new version.
Top comments (0)