DEV Community

Cover image for HoneyDrunk.Transport: A Transport Agnostic Messaging Framework for .NET
Tatted Dev
Tatted Dev

Posted on

HoneyDrunk.Transport: A Transport Agnostic Messaging Framework for .NET

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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";
    });
Enter fullscreen mode Exit fullscreen mode

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   │
└─────────────────────┘                   └─────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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>();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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   │
└───────────────┘    └───────────────┘    └───────────────┘
Enter fullscreen mode Exit fullscreen mode

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";
});
Enter fullscreen mode Exit fullscreen mode

Azure Storage Queue

services.AddHoneyDrunkStorageQueueTransport(options =>
{
    options.ConnectionString = "...";
    options.QueueName = "notifications";
    options.MaxConcurrency = 10;
    options.BatchProcessingConcurrency = 4;  // 40 total concurrent
});
Enter fullscreen mode Exit fullscreen mode

InMemory Transport

services.AddHoneyDrunkTransportCore()
    .AddHoneyDrunkInMemoryTransport();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)