Marten Backed Message Persistence Edit on GitHub


To use Jasper's version of guaranteed delivery with store and forward messaging backed by Marten and the Postgresql database:

  1. Install the Jasper.Marten library via Nuget
  2. Import the MartenBackedPersistence extension in your JasperRegistry as shown in the code below

public class AppUsingMartenMessagePersistence : JasperRegistry
{
    public AppUsingMartenMessagePersistence()
    {
        // Use this line to activate the Marten-backed
        // message persistence for durable, store and forward
        // messaging
        Include<MartenBackedPersistence>();

        // "config" is the ASP.Net Core IConfiguration for the application
        // "options" is the Marten StoreOptions configuration object
        Settings.ConfigureMarten((context, options) =>
        {
            options.Connection(context.Configuration["marten_database"]);

            // Other Marten configuration
        });

        // Use a "durable" TCP listener at port
        // 2222 where the incoming messages will be
        // persisted with Marten upon receipt and
        // deleted only when the message is successfully
        // processed
        Transports.DurableListenerAt(2222);
    }
}

There's also a shorthand method now that does the equivalent:


public class MartenUsingApp : JasperRegistry
{
    public MartenUsingApp()
    {
        // This registers the message persistence as well as
        // configuring Marten inside your application
        Settings.PersistMessagesWithMarten((context, options) =>
        {
            // Configure the Marten StoreOptions
            options.Connection(context.Configuration.GetConnectionString("database"));
        });
    }
}

"Outbox" Pattern Usage

Using the Marten-backed persistence, you can take advantage of Jasper's implementation of the "outbox" pattern where outgoing messages are persisted as part of a native database transaction before being sent to the outgoing transports. The purpose of this pattern is to achieve guaranteed messaging and consistency between the outgoing messages and the current transaction without being forced to use distributed, two phase transactions between your application database and the outgoing queues like RabbitMQ.

To see the outbox pattern in action, consider this ASP.Net Core MVC controller action method:


public async Task<IActionResult> PostCreateUser(
    [FromBody] CreateUser user,
    [FromServices] IMessageContext context,
    [FromServices] IDocumentSession session)
{
    await context.EnlistInTransaction(session);

    session.Store(new User {Name = user.Name});

    var @event = new UserCreated {UserName = user.Name};

    await context.Publish(@event);

    await session.SaveChangesAsync();

    return Ok();
}

A couple notes here:

  • The IMessageContext.EnlistInTransaction(IDocumentSession) method is an extension method in the Jasper.Marten library. When it is called, it tells the IMessageContext to register any outgoing messages to be persisted by that IDocumentSession when the Marten session is saved
  • No messages will actually be placed into Jasper's outgoing, sender queues until the session is successfully saved
  • When the session is saved, the outgoing envelopes will be persisted in the same native Postgresql database, then actually sent to the outgoing transport sending agents

Using the outbox pattern, as long as your transaction is successfully committed, the outgoing messages will eventually be sent out, even if the running system somehow manages to get shut down between the transaction being committed and the messages being successfully sent to the recipients or even if the recipient services are temporarily down and unreachable.

The outbox usage is a little bit easier to use within a Jasper message handler action decorated with the [MartenTransaction] attribute as shown below:


[Transactional]
public static UserCreated Handle(CreateUser message, IDocumentSession session)
{
    session.Store(new User {Name = message.Name});

    return new UserCreated {UserName = message.Name};
}

By decorating the action with that attribute, Jasper.Marten will inject a little bit of code around that method to enlist the current message context into the current Marten IDocumentSession, and the outgoing UserCreated message would be persisted as an outgoing envelope when the session is successfully saved.