Skip to main content

Messaging Patterns

Dispatch provides implementations of common messaging patterns for building reliable distributed systems.

Before You Start

  • .NET 8.0+ (or .NET 9/10 for latest features)
  • Install the required packages:
    dotnet add package Excalibur.Dispatch
  • Familiarity with handlers and pipeline concepts

Start Here

If you are new to reliable messaging, start with these two guides:

  1. Idempotent Consumer Guide — Why messages get duplicated and how the Outbox and Inbox patterns give you effective exactly-once processing.
  2. Error Handling & Recovery Guide — What happens when a message fails: retries, circuit breakers, dead letter queues, and recovery.

Available Patterns

PatternPurposeWhen to Use
Idempotent Consumer GuideUnderstanding reliable messagingNew to distributed systems or Dispatch patterns
Error Handling & Recovery GuideUnderstanding failure handlingNeed to understand retries, DLQ, and recovery
OutboxReliable message publishingAtomic writes with database transactions
InboxIdempotent message processingPrevent duplicate processing
Claim CheckLarge message handlingMessages exceed transport limits
Dead LetterFailed message handlingMessage processing fails repeatedly
RoutingMessage routing rulesRoute messages to different handlers/transports
StreamingAsync stream processingLarge datasets, positional awareness

Outbox Pattern

The outbox pattern ensures messages are published reliably by storing them in the same transaction as your domain changes.

// Configure outbox with the fluent builder
services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);
});
services.AddExcaliburOutbox(outbox =>
{
outbox.UseSqlServer(connectionString)
.WithProcessing(p => p.PollingInterval(TimeSpan.FromSeconds(5)))
.EnableBackgroundProcessing();
});

// In your handler
public class CreateOrderHandler : IActionHandler<CreateOrderAction>
{
private readonly IOutbox _outbox;
private readonly IDbContext _db;

public async Task HandleAsync(CreateOrderAction action, CancellationToken ct)
{
using var transaction = await _db.BeginTransactionAsync(ct);

// Save domain changes
var order = new Order { /* ... */ };
await _db.Orders.AddAsync(order, ct);

// Store message in outbox (same transaction)
await _outbox.AddAsync(new OrderCreatedEvent(order.Id), ct);

await transaction.CommitAsync(ct);
// Message will be published by background processor
}
}

Learn more: Outbox Pattern

Inbox Pattern

The inbox pattern provides idempotent message processing by tracking processed messages.

// Register inbox store
services.AddSqlServerInboxStore(connectionString);
// Or with options:
services.AddSqlServerInboxStore(options =>
{
options.ConnectionString = connectionString;
options.SchemaName = "messaging";
options.TableName = "inbox_messages";
});

// Messages are automatically deduplicated
public class OrderCreatedHandler : IEventHandler<OrderCreatedEvent>
{
public async Task HandleAsync(OrderCreatedEvent @event, CancellationToken ct)
{
// This handler is guaranteed to run only once per message ID
await _emailService.SendOrderConfirmationAsync(@event.OrderId, ct);
}
}

Learn more: Inbox Pattern

Claim Check Pattern

Store large message payloads externally and pass references:

// Register claim check with a provider
services.AddClaimCheck<AzureBlobClaimCheckProvider>(options =>
{
options.ConnectionString = blobConnectionString;
options.ContainerName = "large-messages";
options.PayloadThreshold = 256_000; // 256 KB
});

// Large payloads automatically stored in blob storage
public record ProcessReportAction(
Guid ReportId,
byte[] LargePayload) : IDispatchAction;

// The framework handles claim check automatically:
// 1. If payload > threshold, store in blob, replace with reference
// 2. On receive, retrieve from blob, restore payload

Learn more: Claim Check Pattern

Pattern Combination

Patterns can be combined for maximum reliability:

// Register Dispatch with the builder
services.AddDispatch(dispatch =>
{
dispatch.AddHandlersFromAssembly(typeof(Program).Assembly);
});

// Outbox for reliable publishing
services.AddExcaliburOutbox(outbox =>
{
outbox.UseSqlServer(connectionString)
.EnableBackgroundProcessing();
});

// Inbox for idempotent processing
services.AddSqlServerInboxStore(connectionString);

// Claim check for large messages
services.AddClaimCheck<AzureBlobClaimCheckProvider>(options =>
{
options.ContainerName = "messages";
options.PayloadThreshold = 256_000;
});

Store Providers

Each pattern supports multiple storage backends:

Outbox Stores

StorePackageUse Case
SQL ServerExcalibur.Outbox.SqlServerSQL Server databases
PostgreSQLExcalibur.Data.PostgresPostgreSQL databases
MongoDBExcalibur.Data.MongoDBMongoDB databases
CosmosDBExcalibur.Outbox.CosmosDbAzure Cosmos DB
DynamoDBExcalibur.Outbox.DynamoDbAWS DynamoDB
FirestoreExcalibur.Outbox.FirestoreGoogle Firestore
In-MemoryExcalibur.Data.InMemoryTesting only

Inbox Stores

StorePackageUse Case
SQL ServerExcalibur.Data.SqlServerSQL Server databases
PostgreSQLExcalibur.Data.PostgresPostgreSQL databases
MongoDBExcalibur.Data.MongoDBMongoDB databases
RedisExcalibur.Data.RedisRedis-backed deduplication
DynamoDBExcalibur.Data.DynamoDbAWS DynamoDB
CosmosDBExcalibur.Data.CosmosDbAzure Cosmos DB
In-MemoryExcalibur.Data.InMemoryTesting only

Claim Check Stores

StorePackageUse Case
Azure BlobExcalibur.Dispatch.PatternsAzure Blob Storage
In-MemoryExcalibur.Dispatch.PatternsTesting only

Monitoring Patterns

Outbox Monitoring

// Health check
services.AddHealthChecks()
.AddOutboxHealthCheck();

// Metrics (via OpenTelemetry)
// - dispatch.outbox.pending_count
// - dispatch.outbox.processing_time
// - dispatch.outbox.retry_count

Inbox Monitoring

services.AddHealthChecks()
.AddInboxHealthCheck();

// Metrics
// - dispatch.inbox.duplicate_count
// - dispatch.inbox.processed_count
// - dispatch.inbox.storage_size

In This Section

See Also

  • Transports - Configure transport infrastructure for patterns like Outbox
  • Event Sourcing - Event-sourced aggregates that use Outbox for publishing
  • Handlers - Action and event handler patterns