Ephemeral Messages with Wolverine

I’ve been able to talk and write a bit about Wolverine in the last couple weeks. This builds on the previous blog posts in this list:

This post is a little bonus content that I accidentally cut from the previous post.

Last time I talked about Wolverine’s support for the transactional outbox pattern for messages that just absolutely have to be delivered. About the same day that I was writing that post, I was also talking with a colleague through a very different messaging scenario where a stream of status updates were being streamed to WebSocket connected clients. In this case, the individual messages being broadcast only had temporary validity, and were quickly obsolete. There’s absolutely no need for message persistence or guaranteed delivery. There’s also no good reason to even attempt to deliver a message in this case that’s more than a few seconds old.

To that end, let’s go back yet again to the command handler for the DebitAccount command, but in this version I’m going to cascade an AccountUpdated message that would ostensibly be broadcast through WebSockets to any connected client:

    [Transactional] 
    public static IEnumerable<object> Handle(
        DebitAccount command, 
        Account account, 
        IDocumentSession session)
    {
        account.Balance -= command.Amount;
     
        // This just marks the account as changed, but
        // doesn't actually commit changes to the database
        // yet. That actually matters as I hopefully explain
        session.Store(account);
 
        // Conditionally trigger other, cascading messages
        if (account.Balance > 0 && account.Balance < account.MinimumThreshold)
        {
            yield return new LowBalanceDetected(account.Id);
        }
        else if (account.Balance < 0)
        {
            yield return new AccountOverdrawn(account.Id);
        }

        // Send out a status update message that is maybe being 
        // broadcast to websocket-connected clients
        yield return new AccountUpdated(account.Id, account.Balance);
    }

Now I need to switch to the Wolverine bootstrapping and configure some explicit routing of the AccountUpdated message. In this case, I’m going to let the WebSocket messaging of the AccountUpdated messages happen from a non-durable, local queue:

builder.Host.UseWolverine(opts =>
{
    // Middleware introduced in previous posts
    opts.Handlers.AddMiddlewareByMessageType(typeof(AccountLookupMiddleware));
    opts.UseFluentValidation();

    // Explicit routing for the AccountUpdated
    // message handling. This has precedence over conventional routing
    opts.PublishMessage<AccountUpdated>()
        .ToLocalQueue("signalr")

        // Throw the message away if it's not successfully
        // delivered within 10 seconds
        
        // THIS CONFIGURATION ITEM WAS ADDED IN v0.9.6
        .DeliverWithin(10.Seconds())
        
        // Not durable
        .BufferedInMemory();
    
    var rabbitUri = builder.Configuration.GetValue<Uri>("rabbitmq-broker-uri");
    opts.UseRabbitMq(rabbitUri)
        // Just do the routing off of conventions, more or less
        // queue and/or exchange based on the Wolverine message type name
        .UseConventionalRouting()
        .ConfigureSenders(x => x.UseDurableOutbox());

});

The call to DeliverWithin(10.Seconds()) puts a rule on the local “signalr” queue that all messages published to that queue will have an effective expiration date of 10 seconds from the point at which the message was published. If the web socket publishing is backed up, or there’s a couple failure/retry cycles that delays the message, Wolverine will discard the message before it’s processed.

This option is perfect for transient status messages that have short shelf lives. Wolverine also lets you happily mix and match durable messaging and transient messages in the same message batch, as I hope is evident in the sample handler method in the first code sample.

Lastly, I used a fluent interface to apply the “deliver within” rule at the local queue level. That can also be applied at the message type level with an attribute like this alternative usage:

// The attribute directs Wolverine to send this message with 
// a "deliver within 5 seconds, or discard" directive
[DeliverWithin(5)]
public record AccountUpdated(Guid AccountId, decimal Balance);

Or lastly, I can set the “deliver within” rule on a message by message basis at the time of sending the message like so:

        // "messaging" is a Wolverine IMessageContext or IMessageBus service 
        // Do the deliver within rule on individual messages
        await messaging.SendAsync(new AccountUpdated(account.Id, account.Balance),
            new DeliveryOptions { DeliverWithin = 5.Seconds() });

I’ll try to sneak in one more post before mostly shutting down for Christmas and New Year’s. Next time up I’d like to talk about Wolverine’s support for grown up “clone n’ go” development through its facilities for configuring infrastructure like Postgresql or Rabbit MQ for you based on your application configuration.

Leave a comment