Passing information between Handlers in the Pipeline

A key constraint of the Pipes and Filters architectural style is that Filters do not share state. One reason is that this limits your ability to recompose the pipeline as steps must follow other steps.

However, when dealing with Handlers that implement orthogonal concerns it can be useful to pass context along the chain. Given that many orthogonal concerns have constraints about ordering anyway, we can live with the ordering constraints imposed by passing context.

The first thing is to avoid adding extra properties to the Command to support handling state for these orthogonal Filter steps in your pipeline. This couples your Command to orthogonal concerns and you really only want to bind it to your Target Handler.

Instead we provide a Request Context and Context Bag as part of the Command Dispatcher which is injected into each Handler in the Pipeline. The lifetime of this Request Context is the lifetime of the Request (although you will need to take responsibility for freeing any unmanaged resources you place into the Context Bag for example when code called after the Handler that inserts the resource into the Bag returns to the Handler).

Using the Context Bag

The Context Bag is a ConcurrentDictionary<string, object> that allows you to pass arbitrary data between handlers in the pipeline.

public class MyContextAwareCommandHandler : RequestHandler<MyCommand>
{
    public static string TestString { get; set; }

    public override MyCommand Handle(MyCommand command)
    {
        LogContext();
        return base.Handle(command);
    }

    private void LogContext()
    {
        TestString = (string)Context.Bag["TestString"];
        Context.Bag["MyContextAwareCommandHandler"] = "I was called and set the context";
    }
}

Internally we use the Context Bag in a number of the Quality of Service supporting Attributes we provide. See Fallback for example.

Request Context Capabilities

Setting Request Context Explicitly

You can set the RequestContext explicitly when calling Send, Publish, or DepositPost methods. This allows you to set properties of the RequestContext for transmission to the RequestHandler instead of having a new context created by the RequestContextFactory for that pipeline.

Partition Key

The PartitionKey allows you to control message routing to specific partitions in messaging systems like Kafka, Azure Service Bus, or AWS Kinesis. This is useful for ensuring related messages are processed in order.

Setting Partition Key via Context Bag:

Or using a PartitionKey object:

Important Notes:

  • This value is optional and may be ignored by the transport if partitioning isn't supported

  • Custom message mappers may choose to ignore this value entirely

  • When not set, the transport's default partitioning strategy will be used

  • Default Brighter mappers will use this value when present

Custom Headers

You can add custom headers to messages dynamically via the Request Context. These headers are merged with any static headers configured on the Publication.

Important Notes:

  • Headers set here take precedence over static header configurations

  • Custom message mappers may ignore these headers entirely

  • Merged with Publication.DefaultHeaders by default Brighter mappers

CloudEvents Extensions

When using CloudEvents, you can add custom extension properties via the Request Context.

These properties will be serialized as CloudEvent extensions in the generated message envelope.

Originating Message

For consumers (subscribers to queues or streams), the Request Context now provides access to the OriginatingMessage. This allows you to examine properties of the message that was received, which is useful for debugging and accessing message metadata.

Use Cases:

  • Debugging: Understand how we got to this request

  • Auditing: Log message metadata for compliance

  • Routing: Access original message routing keys

  • Headers: Read custom headers that weren't mapped to the command

Important: This is not thread-safe; the assumption is that you set this from a single thread and access the message from multiple threads. It is not intended to be set from multiple threads.

OpenTelemetry Span

The Request Context provides access to the current Span (Activity) for adding custom OpenTelemetry attributes, events, and tags.

See Telemetry for more information on OpenTelemetry integration.

Destination Override

You can override the destination topic and CloudEvents type for a message using the Destination property.

This allows runtime routing decisions based on command content or business rules.

Resilience Context

The Request Context integrates with Polly's ResiliencePipeline (V8+) for resilience operations. The ResilienceContext can be used to share data across different resilience policies during execution.

See Polly Resilience Pipeline for more information.

Resilience Pipeline Registry

Access pre-configured resilience pipelines by name:

Well-Known Context Bag Keys

Brighter provides well-known keys for the Context Bag via the RequestContextBagNames class:

Using these constants ensures consistency and avoids magic strings:

Best Practices

1. Use Well-Known Keys

2. Clean Up Resources

If you place unmanaged resources in the Context Bag, ensure they are properly disposed:

3. Document Custom Context Keys

If you use custom context bag keys, document them clearly:

4. Check for Null Before Accessing Properties

5. Use Explicit RequestContext for Important Metadata

Summary

The Request Context provides a powerful mechanism for passing information between handlers in a pipeline:

  • Context Bag: Pass arbitrary data between handlers

  • Partition Key: Control message routing to specific partitions

  • Custom Headers: Add dynamic headers to messages

  • CloudEvents Extensions: Add custom CloudEvents properties

  • Originating Message: Access original message metadata in consumers

  • OpenTelemetry Span: Add custom traces and attributes

  • Destination Override: Dynamic message routing

  • Resilience Context: Integration with Polly resilience pipelines

Use these capabilities judiciously to avoid coupling your commands to orthogonal concerns while enabling necessary cross-cutting functionality.

Last updated

Was this helpful?