Transactional Middleware Edit on GitHub


Assuming that the Jasper.Persistence.Marten Nuget is referenced by your project, you can explicitly apply transactional middleware to a message or HTTP handler action with the [Transactional] attribute as shown below:


public class CreateDocCommandHandler
{
    [Transactional]
    public void Handle(CreateDocCommand message, IDocumentSession session)
    {
        session.Store(new FakeDoc {Id = message.Id});
    }
}

Doing this will simply insert a call to IDocumentSession.SaveChangesAsync() after the last handler action is called within the generated MessageHandler. This effectively makes a unit of work out of all the actions that might be called to process a single message.

This 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:


public class CommandsAreTransactional : IHandlerPolicy
{
    public void Apply(HandlerGraph graph, JasperGenerationRules rules)
    {
        // Important! Create a brand new TransactionalFrame
        // for each chain
        graph
            .Chains
            .Where(x => x.MessageType.Name.EndsWith("Command"))
            .Each(x => x.Middleware.Add(new TransactionalFrame()));
    }
}

Then add the policy to your application like this:


public class CommandsAreTransactionalApp : JasperRegistry
{
    public CommandsAreTransactionalApp()
    {
        // And actually use the policy
        Handlers.GlobalPolicy<CommandsAreTransactional>();
    }
}

Customizing How the Session is Created

By default, using [Transactional] or just injecting an IDocumentSession with the Marten integration will create a lightweight session in Marten using the IDocumentStore.LightweightSession() call. However, Marten has many other options to create sessions with different transaction levels, heavier identity map behavior, or by attaching custom listeners. To allow you to use the full range of Marten behavior, you can choose to override the mechanics of how a session is opened for any given message handler by just placing a method called OpenSession() on your handler class that returns an IDocumentSession. If Jasper sees that method exists, it will call that method to create your session.

Here's an example from the tests:


public class SessionUsingBlock2
{
    // This method will be used to create the IDocumentSession
    // that will be used by the two Consume() methods below
    public IDocumentSession OpenSession(IDocumentStore store)
    {
        // Here I'm opting to use the heavier,
        // automatic dirty state checking
        return store.DirtyTrackedSession();
    }


    public void Consume(Message1 message, IDocumentSession session)
    {
    }

    [Transactional]
    public void Consume(Message2 message, IDocumentSession session)
    {
    }
}