V10 Migration Guide
How-to · Applies to Brighter V10
V10 Migration Overview
Brighter V10 introduces significant improvements and new features while maintaining a clear migration path from V9. This guide provides step-by-step instructions for upgrading your application to V10, addressing breaking changes, and adopting new features.
Key Changes in V10:
Cloud Events support
OpenTelemetry Semantic Conventions
Default Message Mappers
Dynamic Message Deserialization
Nullable Reference Types (breaking)
Simplified Configuration (breaking)
Reactor and Proactor terminology (breaking)
Polly Resilience Pipeline v8 (breaking)
Request Context enhancements
InMemory options for testing
Transport improvements (PostgreSQL, RabbitMQ, Kafka, AWS)
Request ID is now an
Idtype
Migration Effort: Most applications can be migrated in 1-4 hours, depending on complexity.
Before You Start
Prerequisites
Backup your code: Commit all changes to version control
Review the release notes: Read Release Notes for V10
Update test suite: Ensure your tests are passing on V9
Check dependencies: Review third-party package compatibility
Recommended Migration Approach
Upgrade in a feature branch: Don't upgrade directly in main/master
Address breaking changes first: Fix compilation errors before adopting new features
Test thoroughly: Run your full test suite after each step
Deploy to staging: Validate in a non-production environment
Monitor production: Watch for issues after deployment
Step 1: Update Package References
Update NuGet Packages
Update all Brighter packages to V10:
Check for Package Conflicts
Step 2: Address Breaking Changes
1. Nullable Reference Types
Breaking Change: Nullable reference types are now enabled across all Brighter projects.
Migration Steps:
Enable nullable reference types in your project (if not already enabled):
Address compiler warnings in Commands, Events, and Handlers:
Before (V9):
After (V10):
Update Message Mappers to handle nullable warnings:
See also: Nullable Reference Types Documentation
2. Simplified Configuration
Breaking Change: Builder methods renamed for clarity.
Before (V9):
After (V10):
AddConsumers extends IServiceCollection and AddProducers extends the Brighter builder it returns, so the consumer registration comes first and the producer registration chains off it. AddBrighter is the entry point when there are no consumers.
Migration Steps:
Replace
UseExternalBuswithAddProducersReplace
AddServiceActivatorwithAddConsumers, and put it first in the chainUpdate property names:
ProducerRegistryinstead of passing directly,DefaultChannelFactoryinstead ofChannelFactory, andInboxConfigurationinstead ofInbox
UseExternalBus()
AddProducers()
Configure message producers (send/publish to external bus)
AddServiceActivator()
AddConsumers()
Configure message consumers (receive from external bus)
options.ChannelFactory
options.DefaultChannelFactory
Supply the channel factory for every subscription that does not name its own
options.Inbox
options.InboxConfiguration
Configure the Inbox used by the consumer pipeline
Terminology: In V10, we use "Dispatcher" to refer to the component that dispatches messages to handlers. The assembly name remains Paramore.Brighter.ServiceActivator for backward compatibility, but documentation and APIs now use "Dispatcher" for clarity.
3. Reactor and Proactor Terminology
Breaking Change: The runAsync flag on Subscription has been renamed to MessagePumpType.
Before (V9):
After (V10):
Migration Table:
runAsync: false
messagePumpType: MessagePumpType.Reactor
Synchronous, blocking I/O
runAsync: true
messagePumpType: MessagePumpType.Proactor
Asynchronous, non-blocking I/O
See also: Reactor and Proactor Documentation
4. Polly Resilience Pipeline
Breaking Change: TimeoutPolicyAttribute is obsolete. Use UseResiliencePipeline on a synchronous handler and UseResiliencePipelineAsync on an async one.
Before (V9):
After (V10):
Define a Resilience Pipeline:
AddBrighterDefault adds the pipelines Brighter itself requires. Assigning your own registry means Brighter does not add them for you, and without them the command processor throws ConfigurationException, so start from it.
Use the new attribute:
Register the pipeline with Brighter:
PolicyRegistry is the obsolete Polly v7 IPolicyRegistry<string>, not this registry.
See also: Policy Retry and Circuit Breaker Documentation
5. Request Context Interface Changes
Breaking Change: IRequestContext has changed shape.
Changed Properties:
Bagis aConcurrentDictionary<string, object>; in V9 it was aDictionary<string, object>Policies, the Polly v7 registry, is obsolete; useResiliencePipelinePoliciesandFeatureSwitchesare nullable
New Members:
Destination: route a request through a particular producer (Destination Override)OriginatingMessage: the message a consumer received (Originating Message)ResiliencePipeline: the Polly v8ResiliencePipelineRegistry<string>(Resilience Pipeline Registry)ResilienceContext: Polly's context for the current execution (Resilience Context)Span: the current OpenTelemetryActivity(OpenTelemetry Span)CreateCopy(): a copy of the context for a new pipeline
Migration: Most code should not be affected unless you implement IRequestContext directly.
If you implement IRequestContext (rare):
Partition keys and custom headers are not properties. IRequestContext has no PartitionKey and no CustomHeaders. Put them in the Bag under the well-known keys RequestContextBagNames.PartitionKey and RequestContextBagNames.Headers, and pass the context with the request. Brighter's default message mappers read both:
A custom mapper reads them only if it asks: Context.GetPartitionKey() and Context.GetHeaders(), in Paramore.Brighter.Extensions. See Partition Key and Custom Headers.
InstrumentationOptions (after 10.7.0)
Not in a released package yet.
IRequestContext.InstrumentationOptionsships after Brighter 10.7.0, which is the current release. It is on Brighter's development branch and in no version you can install today, so an implementation built against 10.7.0 compiles without it; treat what follows as the change as it will ship.
Breaking Change: IRequestContext gains a required InstrumentationOptions member.
It carries the instrumentation options configured for the pipeline that created the context, so middleware handlers can gate their own telemetry — on InstrumentationOptions.Brighter, for example — without needing to know how the Command Processor was configured.
This is source-breaking for direct implementors of IRequestContext only. Paramore.Brighter targets netstandard2.0, which has no default interface members, so the member is plain and abstract: any third-party or test type implementing the interface fails to compile until it adds one line.
The shipped RequestContext implements it on the development branch, defaulting to InstrumentationOptions.All, so code that uses the shipped context needs no change.
6. Request Id and CorrelationId type change
In V9 the Request type used a Guid to represent the identity of a Command or Event. In V10, as part of a move away from primitives, we have changed this to be a type Id. An Id can be constructed from a string using its ToString() method. If you have used a Guid then you will need to turn the Guid into a string.
Within IRequest both Id and CorrelationId both use the type Id in V10.
If you were using a Guid to create a random identity, you can just use Id.Random() instead which has the same behavior.
Before:
After:
Step 3: Adopt New Features (Optional)
1. Cloud Events Support
V10 adds full Cloud Events specification support.
Benefits:
Standardized event metadata
Better interoperability with other systems
Rich event context information
Adoption Steps:
Update Publication to include Cloud Events properties:
Use Cloud Events headers in your mapper (optional):
See also: Cloud Events Documentation
2. Default Message Mappers
V10 allows you to omit message mappers for simple JSON serialization.
Benefits:
Less boilerplate code
Faster development
Still supports custom mappers for complex scenarios
Before (V9) - Required Mapper:
After (V10) - No Mapper Needed:
When to use custom mappers:
Complex transformations
Non-JSON formats (XML, Protobuf, etc.)
Transform pipelines (encryption, compression, claim check)
Custom header mapping
See also: Message Mappers Documentation
3. Dynamic Message Deserialization
V10 supports multiple message types on the same channel.
Benefits:
Content-based routing
Flexible message processing
Better use of infrastructure
Example:
See also: Dynamic Deserialization Documentation
4. OpenTelemetry Semantic Conventions
V10 adopts OpenTelemetry Semantic Conventions for messaging.
Impact: Trace spans will have different names and attributes than V9.
Benefits:
Standard messaging conventions
Better integration with APM tools
Consistent telemetry across systems
Migration:
Update dashboards and alerts to use new span names
Review trace queries for compatibility
Test observability in staging environment
V9 Span Names:
Paramore.Brighter.CommandProcessor.SendParamore.Brighter.MessagePump.Receive
V10 Span Names (OTel Semantic Conventions):
messaging.sendmessaging.receivemessaging.process
See also: Telemetry Documentation
5. InMemory Options for Testing
V10 provides comprehensive InMemory implementations for testing.
Benefits:
Fast test execution
No external dependencies
Easy CI/CD integration
Example:
See also: InMemory Options Documentation
6. Kafka KIP-848 Consumer Group Protocol
V10 adds support for KIP-848, Kafka's next-generation consumer group protocol with broker-driven partition assignment (requires Apache Kafka 4.0 or later).
Not a breaking change: Existing subscriptions continue to use the classic consumer group protocol by default.
Obsoleted properties: KafkaSubscription.SessionTimeout and KafkaSubscription.PartitionAssignmentStrategy are now marked [Obsolete] and will be removed in a future version. They still work (their values are used as back-fill defaults), but new code should set these values on a ClassicGroupProtocol:
Before:
After:
Opting into KIP-848:
See also: Kafka Configuration — Consumer Group Protocol (KIP-848)
Step 4: Test Your Migration
Unit Tests
Run existing unit tests:
Address test failures:
Update mocks for
IRequestContextnew propertiesUpdate assertions for OpenTelemetry span names
Fix nullable reference warnings
Integration Tests
Test with InMemory components (fast):
Test with real transports (slower, more complete):
Performance Testing
Compare V9 and V10 performance:
Expected: V10 should have similar or better performance due to optimizations.
Step 5: Deploy to Staging
Pre-Deployment Checklist
Deployment Steps
Deploy to staging environment
Run smoke tests
Monitor metrics:
Message processing latency
Error rates
Resource usage (CPU, memory)
Check logs for warnings or errors
Validate telemetry (traces, metrics)
Monitoring
Watch for:
Increased error rates
Performance degradation
OpenTelemetry trace issues
Null reference exceptions
Step 6: Deploy to Production
Production Deployment
Deploy during low-traffic period
Use blue-green or canary deployment if possible
Monitor closely for first 24 hours
Have rollback plan ready
Post-Deployment
Review logs for issues
Check application metrics
Validate message processing
Monitor OpenTelemetry traces
Common Migration Issues
Issue 1: Nullable Reference Warnings
Symptom: CS8618, CS8600, CS8602 warnings
Solution: See Nullable Reference Types Documentation
Issue 2: Method Not Found
Symptom: UseExternalBus method not found
Solution: Replace with AddProducers
Issue 3: Property Not Found on Subscription
Symptom: runAsync property does not exist
Solution: Use messagePumpType: MessagePumpType.Proactor or MessagePumpType.Reactor
Issue 4: TimeoutPolicy Not Working
Symptom: TimeoutPolicyAttribute marked as obsolete
Solution: Migrate to UseResiliencePipeline with Polly v8
Issue 5: Telemetry Spans Changed
Symptom: Dashboard queries not finding spans
Solution: Update queries to use OpenTelemetry Semantic Convention names
Rollback Plan
If you need to roll back to V9:
Revert package versions:
Revert code changes:
Redeploy V9 version
Investigate issues before attempting V10 migration again
Getting Help
Resources
Reporting Issues
If you encounter issues during migration:
Check existing issues: Search GitHub Issues
Create a new issue with:
V9 and V10 versions
Minimal reproduction example
Error messages and stack traces
Environment details (.NET version, OS, transport)
V10 Migration Summary
Migrating to Brighter V10 involves:
Update packages to V10
Address breaking changes:
Nullable reference types
Simplified configuration API
Reactor/Proactor terminology
Polly Resilience Pipeline
Adopt new features (optional):
Cloud Events
Default message mappers
Dynamic deserialization
OpenTelemetry conventions
InMemory testing options
Test thoroughly
Deploy to staging, then production
Monitor and address any issues
Most migrations can be completed in 1-4 hours. The breaking changes are straightforward, and V10 provides significant improvements in features, performance, and developer experience.
Good luck with your migration! 🚀
Last updated
Was this helpful?
