Skip to main content

MessageContext Design

This document explains the architectural design of IMessageContext, its evolution, and the rationale behind the feature-based decomposition.

Overview​

IMessageContext is the central context object that flows through the entire message processing pipeline. It follows the pattern of Microsoft.AspNetCore.Http.HttpContext: a minimal set of core properties plus a typed feature collection for extensibility.

It serves as:

  1. Metadata Container - Core message IDs (MessageId, CorrelationId, CausationId)
  2. Middleware Communication - Sharing data within the pipeline via Items dictionary
  3. Feature Collection - Typed access to cross-cutting concerns (identity, routing, processing state, validation, timeout, rate limiting, transactions) via Features dictionary

Design Principles​

1. Microsoft-First: Minimal Core Interface​

Following the HttpContext pattern, IMessageContext exposes only 8 properties. All cross-cutting concerns are accessed through typed feature interfaces, keeping the core interface stable as new concerns are added.

Before: 40 direct properties on IMessageContext After: 8 core properties + 7 feature interfaces

2. Type Safety via Feature Interfaces​

Feature interfaces provide:

  • Compile-time type checking
  • IntelliSense support
  • No runtime casting or type errors
  • Self-documenting API
  • Independent testability and mockability

3. Separation of Core vs Features vs Items​

Core Properties (8) - Data needed by the framework on every message:

  • Identity: MessageId, CorrelationId, CausationId
  • Payload: Message, Result
  • Infrastructure: RequestServices, Items, Features

Feature Interfaces - Typed cross-cutting concerns:

  • IMessageIdentityFeature - UserId, TenantId, SessionId, WorkflowId, ExternalId, TraceParent
  • IMessageProcessingFeature - ProcessingAttempts, IsRetry, FirstAttemptTime, DeliveryCount
  • IMessageValidationFeature - ValidationPassed, ValidationTimestamp
  • IMessageTimeoutFeature - TimeoutExceeded, TimeoutElapsed
  • IMessageRateLimitFeature - RateLimitExceeded, RateLimitRetryAfter
  • IMessageRoutingFeature - RoutingDecision, PartitionKey, Source
  • IMessageTransactionFeature - Transaction, TransactionId

Items Dictionary - Data that varies by transport or is user-defined:

  • RabbitMQ headers, SQS attributes
  • HTTP headers, CloudEvents attributes
  • Custom user data

Core Properties​

string? MessageId // Unique message identifier
string? CorrelationId // Groups related messages
string? CausationId // Links to parent message
IDispatchMessage? Message // The message payload
object? Result // Handler return value
IServiceProvider RequestServices // Scoped DI container
IDictionary<string, object> Items // Transport metadata, custom data
IDictionary<Type, object> Features // Typed feature collection

Feature Access Patterns​

Three levels of feature access are available:

using Excalibur.Dispatch.Features;

// 1. Generic feature access
var identity = context.GetFeature<IMessageIdentityFeature>();
context.SetFeature<IMessageIdentityFeature>(new MessageIdentityFeature { TenantId = "acme" });

// 2. Typed convenience (get or create with default implementation)
var processing = context.GetOrCreateProcessingFeature();
processing.ProcessingAttempts++;

// 3. Property-level convenience (read-only shortcuts)
var tenantId = context.GetTenantId();
var isRetry = context.GetIsRetry();

Items Dictionary​

The Items dictionary is for data that:

  1. Varies by transport - RabbitMQ has delivery tags; SQS has receipt handles
  2. Has unpredictable keys - HTTP headers, CloudEvents attributes
  3. Is accessed infrequently - Setup once, read once

Transport-Specific Examples​

// RabbitMQ
context.Items["rabbitmq.exchange"] = exchange;
context.Items["rabbitmq.deliveryTag"] = deliveryTag;

// HTTP Headers
context.Items["Authorization"] = bearerToken;
context.Items["X-Custom-Header"] = value;

// CloudEvents
context.Items["ce.type"] = "com.example.event";
context.Items["ce.source"] = "/my-service";

See MessageContext Items Usage for complete documentation.

Context Propagation​

When dispatching child messages, cross-cutting concerns propagate automatically:

var childContext = context.CreateChildContext();
// Propagated: CorrelationId, IMessageIdentityFeature, IMessageRoutingFeature.Source
// Set automatically: CausationId = parent.MessageId, new MessageId
// NOT copied: Items dictionary, processing/validation/timeout features

This enables distributed tracing without manual plumbing.

Thread Safety​

MessageContext is designed to be thread-safe:

  • Property setters use standard CLR mechanisms
  • Items dictionary can be accessed from middleware on different threads
  • The context is scoped to a single message processing operation

Lifetime & Pooling​

Message contexts are pooled for performance:

  1. Acquired from pool when message enters pipeline
  2. Used throughout message processing
  3. Cleared when processing completes (features and items cleared)
  4. Returned to pool for reuse

Evolution History​

StageChange
InitialIMessageContext with 40 direct properties
CurrentDecomposed to 8 core + 7 feature interfaces

The decomposition follows the Microsoft-First design guidelines, splitting a large interface into focused, segregated capabilities accessed through typed feature interfaces.

Next Steps​

See Also​