Skip to content

The search box in the website knows all the secrets—try it!

For any queries, join our Discord Channel to reach us faster.

JasperFx Logo

JasperFx provides formal support for Wolverine and other JasperFx libraries. Please check our Support Plans for more details.

Transactional Middleware

WARNING

When using the transactional middleware with Marten, Wolverine is assuming that there will be a single, atomic transaction for the entire message handler. Because of the integration with Wolverine's outbox and the Marten IDocumentSession, it is very strongly recommended that you do not call IDocumentSession.SaveChangesAsync() yourself as that may result in unexpected behavior in terms of outgoing messages.

TIP

You will need to make the IServiceCollection.AddMarten(...).IntegrateWithWolverine() call to add this middleware to a Wolverine application.

It is no longer necessary to mark a handler method with [Transactional] if you choose to use the AutoApplyTransactions() option as shown below:

cs
using var host = await Host.CreateDefaultBuilder()
    .UseWolverine(opts =>
    {
        opts.Services.AddMarten("some connection string")
            .IntegrateWithWolverine();

        // Opt into using "auto" transaction middleware
        opts.Policies.AutoApplyTransactions();
    }).StartAsync();

snippet source | anchor

With this enabled, Wolverine will automatically use the Marten transactional middleware for handlers that have a dependency on IDocumentSession (meaning the method takes in IDocumentSession or has some dependency that itself depends on IDocumentSession) as long as the IntegrateWithWolverine() call was used in application bootstrapping.

Opting Out with [NonTransactional]

When using AutoApplyTransactions(), there may be specific handlers or HTTP endpoints where you want to explicitly opt out of transactional middleware even though they use IDocumentSession. You can do this with the [NonTransactional] attribute:

cs
using Wolverine.Attributes;

public static class MySpecialHandler
{
    // This handler will NOT have transactional middleware applied
    // even when AutoApplyTransactions() is enabled
    [NonTransactional]
    public static void Handle(MyCommand command, IDocumentSession session)
    {
        // You're managing the session yourself here
    }
}

The [NonTransactional] attribute can be placed on individual handler methods or on the handler class itself to opt out all methods:

cs
using Wolverine.Attributes;

// No methods in this handler class will have
// transactional middleware applied
[NonTransactional]
public static class NonTransactionalHandlers
{
    public static void Handle(CommandA command, IDocumentSession session)
    {
        // ...
    }

    public static void Handle(CommandB command, IDocumentSession session)
    {
        // ...
    }
}

This also works for Wolverine HTTP endpoints:

cs
using Wolverine.Attributes;
using Wolverine.Http;

public static class MyEndpoints
{
    // This endpoint will NOT use transactional middleware
    [NonTransactional]
    [WolverinePost("/my-non-transactional-endpoint")]
    public static string Post(IDocumentSession session)
    {
        return "not transactional";
    }
}

In the previous section we saw an example of incorporating Wolverine's outbox with Marten transactions. We also wrote a fair amount of code to do so that could easily feel repetitive over time. Using Wolverine's transactional middleware support for Marten, the long hand handler above can become this equivalent:

cs
// Note that we're able to avoid doing any kind of asynchronous
// code in this handler
[Transactional]
public static OrderCreated Handle(CreateOrder command, IDocumentSession session)
{
    var order = new Order
    {
        Description = command.Description
    };

    // Register the new document with Marten
    session.Store(order);

    // Utilizing Wolverine's "cascading messages" functionality
    // to have this message sent through Wolverine
    return new OrderCreated(order.Id);
}

snippet source | anchor

Or if you need to take more control over how the outgoing OrderCreated message is sent, you can use this slightly different alternative:

cs
[Transactional]
public static ValueTask Handle(
    CreateOrder command,
    IDocumentSession session,
    IMessageBus bus)
{
    var order = new Order
    {
        Description = command.Description
    };

    // Register the new document with Marten
    session.Store(order);

    // Utilizing Wolverine's "cascading messages" functionality
    // to have this message sent through Wolverine
    return bus.SendAsync(
        new OrderCreated(order.Id),
        new DeliveryOptions { DeliverWithin = 5.Minutes() });
}

snippet source | anchor

In both cases Wolverine's transactional middleware for Marten is taking care of registering the Marten session with Wolverine's outbox before you call into the message handler, and also calling Marten's IDocumentSession.SaveChangesAsync() afterward. Used judiciously, this might allow you to avoid more messy or noisy asynchronous code in your application handler code.

TIP

This [Transactional] attribute can appear on either the handler class that will apply to all the actions on that class, or on a specific action method.

If so desired, you can also use a policy to apply the Marten transaction semantics with a policy. As an example, let's say that you want every message handler where the message type name ends with "Command" to use the Marten transaction middleware. You could accomplish that with a handler policy like this:

cs
public class CommandsAreTransactional : IHandlerPolicy
{
    public void Apply(IReadOnlyList<HandlerChain> chains, GenerationRules rules, IServiceContainer container)
    {
        // Important! Create a brand new TransactionalFrame
        // for each chain
        chains
            .Where(chain => chain.MessageType.Name.EndsWith("Command"))
            .Each(chain => chain.Middleware.Add(new CreateDocumentSessionFrame(chain)));
    }
}

snippet source | anchor

Then add the policy to your application like this:

cs
using var host = await Host.CreateDefaultBuilder()
    .UseWolverine(opts =>
    {
        // And actually use the policy
        opts.Policies.Add<CommandsAreTransactional>();
    }).StartAsync();

snippet source | anchor

Using IDocumentOperations 3.14

When using the transactional middleware with Marten, it's best to not directly call IDocumentSession.SaveChangesAsync() yourself because that negates the transactional middleware's ability to mark the transaction boundary and can cause unexpected problems with the outbox. As a way of preventing this problem, you can choose to directly use Marten's IDocumentOperations as an argument to your handler or endpoint methods, which is effectively IDocumentSession minus the ability to commit the ongoing unit of work with a SaveChangesAsync API.

Here's an example:

cs
public class CreateDocCommand2Handler
{
    [Transactional]
    public void Handle(
        CreateDocCommand2 message, 
        
        // This is the IDocumentSession for the handler &
        // transactional middleware, it's just that you're
        // going to use the slimmer interface that won't let
        // you accidentally call SaveChangesAysnc
        IDocumentOperations operations)
    {
        operations.Store(new FakeDoc { Id = message.Id });
    }
}

snippet source | anchor

Released under the MIT License.