githubEdit

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 Architecturearrow-up-right, 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 IRequest and IRequestContext, returns List<Type> of handlers

  • handlerTypes: 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:

  1. CloudEvents type determines the Request type

  2. 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

Sample Code

Full working examples can be found in the Brighter samples:

  • Agreement Dispatcher: Brighter/samples/WebAPI/ - Examples of dynamic handler selection

  • Multi-handler: Various samples showing handler pipeline composition

Last updated

Was this helpful?