Distributed systems love to trap you in vendor gravity. One transport today becomes a rewrite tomorrow. When I started wiring together services for HoneyDrunk’s distributed grid, the usual messaging libraries all had the same problem: coupling. Everything leaked broker assumptions.
So I built HoneyDrunk.Transport to break that pattern.
What It Is
HoneyDrunk.Transport is a transport-agnostic messaging abstraction for .NET. Your code looks identical whether you're using Azure Service Bus, Azure Storage Queues, or the InMemory transport.
// This code works identically whether you're using
// Azure Service Bus, Storage Queues, or InMemory transport
public class OrderService(IMessagePublisher publisher, IGridContext gridContext)
{
public async Task CreateOrderAsync(Order order, CancellationToken ct)
{
await publisher.PublishAsync(
destination: "orders.created",
message: new OrderCreated(order.Id, order.CustomerId),
gridContext: gridContext,
cancellationToken: ct);
}
}
Your handlers stay the same too.
public class OrderCreatedHandler : IMessageHandler<OrderCreated>
{
public async Task<MessageProcessingResult> HandleAsync(
OrderCreated message,
MessageContext context,
CancellationToken ct)
{
var correlationId = context.GridContext?.CorrelationId;
var tenantId = context.GridContext?.TenantId;
await ProcessOrderAsync(message, ct);
return MessageProcessingResult.Success;
}
}
Why It Exists
1. Swap transports without rewriting code
// Development
services.AddHoneyDrunkTransportCore()
.AddHoneyDrunkInMemoryTransport();
// Production
services.AddHoneyDrunkTransportCore()
.AddHoneyDrunkServiceBusTransport(options =>
{
options.FullyQualifiedNamespace = "mynamespace.servicebus.windows.net";
options.Address = "orders-queue";
});
2. Distributed context propagation
Node A (Order Service) Node B (Payment Service)
┌─────────────────────┐ ┌─────────────────────┐
│ CorrelationId: abc │ ──── Queue ────▶ │ CorrelationId: abc │
│ TenantId: tenant-1 │ │ TenantId: tenant-1 │
│ ProjectId: proj-1 │ │ ProjectId: proj-1 │
└─────────────────────┘ └─────────────────────┘
3. Middleware pipeline
services.AddHoneyDrunkTransportCore(options =>
{
options.EnableTelemetry = true; // OpenTelemetry spans
options.EnableLogging = true; // Structured logging
options.EnableCorrelation = true; // Grid context propagation
});
// Add custom middleware
services.AddMessageMiddleware<TenantResolutionMiddleware>();
4. Configurable error handling
var strategy = new ConfigurableErrorHandlingStrategy(maxDeliveryCount: 5)
.RetryOn<TimeoutException>(TimeSpan.FromSeconds(5))
.RetryOn<HttpRequestException>(TimeSpan.FromSeconds(10))
.DeadLetterOn<ValidationException>()
.DeadLetterOn<ArgumentNullException>();
services.AddSingleton<IErrorHandlingStrategy>(strategy);
Architecture at a Glance
┌─────────────────────────────────────────────────────────────────┐
│ Your Application │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ OrderService │ │ PaymentHandler │ │ Custom │ │
│ │ (IMessagePub) │ │ (IMessageHandler)│ │ Middleware │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
└───────────┼─────────────────────┼─────────────────────┼─────────┘
│ │ │
┌───────────▼─────────────────────▼─────────────────────▼─────────┐
│ HoneyDrunk.Transport │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Abstractions │ │ Pipeline │ │ Configuration│ │
│ │ IMessagePub │ │ Middleware │ │ ErrorStrategy│ │
│ │ IMessageHdlr │ │ Telemetry │ │ RetryOptions │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ ServiceBus │ │ StorageQueue │ │ InMemory │
│ Transport │ │ Transport │ │ Transport │
└───────────────┘ └───────────────┘ └───────────────┘
Transport Adapters
Azure Service Bus
services.AddHoneyDrunkServiceBusTransport(options =>
{
options.FullyQualifiedNamespace = "mynamespace.servicebus.windows.net";
options.EntityType = ServiceBusEntityType.Topic;
options.Address = "orders-topic";
options.SubscriptionName = "order-processor";
options.MaxConcurrency = 10;
// Blob fallback for large messages
options.BlobFallback.Enabled = true;
options.BlobFallback.AccountUrl = "https://myaccount.blob.core.windows.net";
});
Azure Storage Queue
services.AddHoneyDrunkStorageQueueTransport(options =>
{
options.ConnectionString = "...";
options.QueueName = "notifications";
options.MaxConcurrency = 10;
options.BatchProcessingConcurrency = 4; // 40 total concurrent
});
InMemory Transport
services.AddHoneyDrunkTransportCore()
.AddHoneyDrunkInMemoryTransport();
Try It Out
dotnet add package HoneyDrunk.Transport
dotnet add package HoneyDrunk.Transport.AzureServiceBus
dotnet add package HoneyDrunk.Transport.StorageQueue
dotnet add package HoneyDrunk.Transport.InMemory
Final Notes
HoneyDrunk.Transport exists because messaging shouldn’t require allegiance to a single broker. If you want clean abstractions, portable handlers, built-in telemetry, and automatic context propagation, it’s worth exploring.
If you try it, let me know how it fits into your stack.
Originally published on TattedDev.com
Top comments (0)