Agreement Dispatcher
Overview
The Agreement Dispatcher is a pattern for routing requests to handlers dynamically based on the request's content or context, rather than using a fixed type-to-handler mapping. This pattern, described by Martin Fowler in Patterns of Enterprise Application Architecture, enables flexible routing logic that can change based on business rules, time, geography, or other runtime conditions.
Brighter supports an Agreement Dispatcher, allowing you to register a lambda function that determines which handler(s) should process a request at runtime.
Standard vs Agreement Dispatcher Routing
Standard 1-to-1 Routing (Default)
In standard Brighter routing, each request type maps to exactly one handler type at compile-time:
services.AddBrighter(options => { })
.Handlers(registry =>
{
// Fixed mapping: MyCommand always goes to MyCommandHandler
registry.Register<MyCommand, MyCommandHandler>();
});Characteristics:
Simple and predictable
Type-safe at compile-time
Fast (no runtime lookup)
Works with
AutoFromAssemblies()Cannot change routing based on request content
Cannot route to different handlers over time
When to use standard routing:
Handler selection doesn't depend on request content
One handler per command/event is sufficient
Simple, straightforward scenarios
This is Brighter's default and recommended approach for most scenarios.
Agreement Dispatcher Routing
Agreement Dispatcher allows dynamic handler selection based on request content or context:
Characteristics:
Flexible routing based on content
Can change behavior over time
Supports multiple handlers
Access to request context
Cannot use
AutoFromAssemblies()Must register handlers explicitly
Small performance overhead (lambda execution)
When to use Agreement Dispatcher:
Handler selection depends on request content
Business rules change over time
Geography or customer-specific routing
A/B testing or feature flags
Multi-tenant routing
Use Cases
1. Time-Based Routing
Route to different handlers as business rules evolve over time:
Scenario: Tax regulations change, but you need to process old orders with old rules and new orders with new rules.
2. Country-Specific Business Logic
Route based on geographical requirements:
Scenario: Payment processing varies significantly by country (regulations, currencies, payment methods).
3. Order Journey Based on Contents
Different order types require different processing workflows:
Scenario: Orders follow different workflows based on their characteristics.
4. Versioning and Migration
Support multiple API versions simultaneously:
Scenario: Maintain backward compatibility while rolling out new API versions.
5. State-Based Routing
Route based on current state or status:
Scenario: Refund processing varies based on order state.
Registration Syntax
Basic Registration
Parameters:
routingFunc: Lambda that takes
IRequestandIRequestContext, returnsList<Type>of handlershandlerTypes: Array of all possible handler types (for DI registration)
Accessing Request Content
The routing function receives IRequest, which you must cast to your specific type:
Why the cast? The registry supports multiple request types, so the lambda signature uses IRequest. You need to cast to access type-specific properties.
Accessing Request Context
The IRequestContext provides additional information:
Returning Multiple Handlers
Agreement Dispatcher can return multiple handlers, but it must still obey the rule that Send expects a single handler and Publish can have zero-to-many handlers. If you return multiple handlers in a Send request pipeline, Brighter will throw an exception.
Synchronous and Asynchronous Registration
Agreement Dispatcher supports both sync and async handlers:
Synchronous Registration
Asynchronous Registration
Note: The routing lambda itself is always synchronous. Only the handler execution is async when using RegisterAsync.
Limitations
Cannot Use AutoFromAssemblies
Agreement Dispatcher requires explicit handler registration:
Why? AutoFromAssemblies() creates fixed 1-to-1 mappings. Agreement Dispatcher needs explicit lambda registration and handler type lists for DI.
Solution: Use .Handlers() to register Agreement Dispatcher routes explicitly:
Handler Types Must Be Provided
You must provide all possible handler types for DI registration:
Why? Brighter registers these handler types with the DI container so they can be resolved at runtime.
Performance Implications
Agreement Dispatcher has a small performance overhead compared to standard routing:
Overhead Breakdown
Standard Routing:
Handler type lookup: Dictionary lookup (~O(1))
No lambda execution
Agreement Dispatcher:
Lambda execution
Handler type lookup: Dictionary lookup (~O(1))
Performance Considerations
For most applications, this overhead is negligible:
Acceptable: Web APIs, message processing, background jobs
Acceptable: 99.9% of scenarios
Consider carefully: Ultra-low latency systems (microsecond SLAs)
Consider carefully: Millions of messages per second
Optimization tip: Keep routing lambdas simple. Avoid expensive operations like database calls or external API calls.
Integration with Dynamic Message Deserialization
Agreement Dispatcher can be combined with Dynamic Message Deserialization for two-level routing:
This provides powerful, flexible routing:
CloudEvents type determines the Request type
Request content determines the Handler
Complete Example
Here's a complete example showing Agreement Dispatcher with multiple routing strategies:
Best Practices
1. Keep Routing Logic Simple
Routing lambdas should be fast and deterministic:
2. Provide Clear Error Messages
Handle unexpected cases gracefully:
3. Document Routing Rules
Document the routing logic for maintainability:
4. Use Standard Routing When Possible
Only use Agreement Dispatcher when you need dynamic routing:
5. List All Possible Handlers
Always provide the complete list of handler types:
Troubleshooting
Handler Not Found Error
Problem: Runtime error saying handler type cannot be resolved.
Cause: Handler type not in the handler types array.
Solution: Add the handler to the array:
AutoFromAssemblies Conflicts
Problem: Agreement dispatcher routes not working with AutoFromAssemblies().
Cause: AutoFromAssemblies() creates fixed mappings.
Solution: Use explicit .Handlers() registration:
Further Reading
Martin Fowler: Agreement Dispatcher - Original pattern description
Dynamic Message Deserialization - Content-based type routing
Request Handlers - Handler basics
Routing - Standard routing in Brighter
Sample Code
Full working examples can be found in the Brighter samples:
Agreement Dispatcher:
Brighter/samples/WebAPI/- Examples of dynamic handler selectionMulti-handler: Various samples showing handler pipeline composition
Last updated
Was this helpful?
