Jasper meets RabbitMQ

For the most part, I’ve been focused on Jasper’s built in socket-based and HTTP transports because they’re compatible with the infrastructure that our older, existing systems at work already use. For pretty easy to understand reasons, other folks in the company are very apprehensive about depending on something new that they’ve never heard of (even though its forebears have been running happily in production for 4-5 years, but I digress). To have another transport option using more industry proven technology, I took a couple days and built out a new transport option in Jasper that uses the well known RabbitMQ message broker to do the actual work of moving message data from service to service.

Getting Started with RabbitMQ within Jasper

There really isn’t that much to do. Assuming that you have RabbitMQ itself stood up (I run it locally on OS X for development, but hosting it in Docker is pretty easy too) and a Jasper application, you just need to install the Jasper.RabbitMQ Nuget into your Jasper application project and configure listeners or publishing rules via “rabbitmq” Uris as shown in the samples below.

From there, you can direct Jasper to listen for messages from a RabbitMQ broker like this:

public class AppListeningToRabbitMQ : JasperRegistry
{
    public AppListeningToRabbitMQ()
    {
        // Port is optional if you're using the default RabbitMQ
        // port of 5672, but it's shown here for completeness
        Transports.ListenForMessagesFrom("rabbitmq://rabbitserver:5672/messages");
    }
}

I’ll discuss how to get around this restriction in a later section, but for right now this configuration implies that the messages received from this queue are coming from another Jasper application or at least an application that is using Jasper’s idioms for envelope metadata through headers.

The Uri structure is specific to Jasper just to identify the connection to a certain RabbitMQ broker by host name, port number, and RabbitMQ specific information like queue names, exchanges, and exchange types.

Likewise, message subscriptions or publishing rules are configured by the same Uri data, with an example publishing rule shown below (when Jasper sees the Uri scheme “rabbitmq,” it knows to delegate to the RabbitMQ transport internally):

public class AppPublishingToRabbitMQ : JasperRegistry
{
    public AppPublishingToRabbitMQ()
    {
        Publish.AllMessagesTo("rabbitmq://rabbitserver:5672/messages");
    }
}

Again, this simplistic example is assuming that there’s another Jasper application on the receiving side of the queue, or at least something that can understand the message metadata and serialized message data sent through the queue.

For the moment, I’ve just been testing with direct exchanges in RabbitMQ. It should be possible as is to use fan out exchanges with the current code, but I was unable to write a successful test proving that really worked locally. The usage of RabbitMQ right now is assuming that Jasper is responsible for the message routing. I’m going to leave the work for a later day, but the “seams” are already in place where we could replace Jasper’s built in message routing to use RabbitMQ topic or header exchanges to offload the actual routing logic to RabbitMQ.

More things to know:

  • Jasper is using the RabbitMQ.Client library to interact with RabbitMQ. If you browse the Jasper documentation, you can see how Jasper.RabbitMQ exposes all the RabbitMQ.Client ConnectionFactory configuration so that you can use any necessary features or configuration for that library (like I don’t know, security?).
  • The RabbitMQ transport can be used either in a “fire and forget,” non-durable manner, or as a durable transport that’s still backed up by Jasper’s support for durable messaging and the outbox pattern I demonstrated in my last post that obviates the need for distributed transactions between your application database and RabbitMQ itself.
  • More below, but you can use the RabbitMQ transport to integrate with other applications that are not themselves Jasper applications or even .Net applications

 

What if Jasper isn’t on the other end?

I did a long, internal presentation on Jasper at work a couple weeks ago that I would charitably describe as a train wreck (Jasper itself did fine, but the presentation went soaring off the rails for me). One of the main axis of pushback about adopting Jasper was that it would lock us into the .Net platform. Besides the ability to just use HTTP services between applications, I tried to say that we’d still be able to do messaging between Jasper/.Net applications and say a Node.js application through RabbitMQ with just a little bit of potential effort to translate between message metadata.

At the point of receiving a message from RabbitMQ or enqueuing a message to RabbitMQ, there’s a little bit of mapping that takes Jasper’s Envelope object that contains the raw message and some tracking metadata, and maps that to the RabbitMQ.Client IBasicProperties model. If there’s a non-Jasper application on the other side of the queue, you may want to do some translation of the metadata using the IEnvelopeMapper interface or subclass the built in DefaultEnvelopeMapper as shown below:

public class CustomEnvelopeMapping : DefaultEnvelopeMapper
{
    public override Envelope ReadEnvelope(byte[] data, IBasicProperties props)
    {
        // Customize the mappings from RabbitMQ headers to
        // Jasper's Envelope values
        return base.ReadEnvelope(data, props);
    }

    public override void WriteFromEnvelope(Envelope envelope, IBasicProperties properties)
    {
        // Customize how Jasper Envelope objects are mapped to
        // the outgoing RabbitMQ message structure
        base.WriteFromEnvelope(envelope, properties);
    }
}

To use your custom envelope mapping, attach it by Uri again like this:

public class CustomizedRabbitMQApp : JasperRegistry
{
    public CustomizedRabbitMQApp()
    {
        Settings.Alter<RabbitMQSettings>(settings =>
        {
            // Retrieve the Jasper "agent" by the full Uri:
            var agent = settings.For("rabbitmq://server1/queue1");

            // Customize or change how Jasper maps Envelopes to and from
            // the RabbitMQ properties
            agent.EnvelopeMapping = new CustomEnvelopeMapping();
        });
    }
}

You may not even need to do this at all because at a minimum, the raw message data, the application id, the message type, and content type (think “application/json” etc) will map correctly to Jasper’s internal structure, but you’d just miss out on some correlation data and the ability to do workflows like saga id tracking and request/reply semantics.

I edited this section a couple times to try to filter out most of the snark and irritation on my part, but I’m sure some of that comes shining through.

 

Why not just use RabbitMQ?

In an internal presentation at work a couple weeks ago, I tried to use this slide to illustrate why we would still use Jasper with RabbitMQ instead of “just using RabbitMQ”:

JasperAndRabbitMQ

Feel free to argue with the details of my Venn diagram up there, but the point I was trying to get across was that there were a lot of application concerns around messaging that simply aren’t part of what RabbitMQ or Kafka or Azure Service Bus does for you. I spent about 20 minutes on this, then demonstrated a lot of Jasper’s features for how it takes raw byte[] data and marshals that to the right command executor in your application with all the error handling, instrumentation, outbox, and middleware fixings you’d expect in a tool like Jasper. And at the end of all that, I got a huge dose of “why aren’t we just using Kafka?” Grrrrrr.

I intend to write a Jasper-flavored version of this question someday soon, but for now, I’ll recommend Sure, You Can Just Use RabbitMQ.

Advertisements

Jasper’s “Outbox” Pattern Support

Jasper supports the “outbox pattern,”  a way to achieve consistency between the outgoing messages that you send out as part of a logical unit of work without having to resort to two phase, distributed transactions between your application’s backing database and whatever queueing technology you might be using. Why do you care? Because consistency is good, and distributed transactions suck, that’s why.

Before you read this, and especially if you’re a coworker freaking out because you think I’m trying to force you to use Postgresql, Jasper is not directly coupled to Postgresql and we will shortly add similar support to what’s shown here for Sql Server message persistence with Dapper and possibly Entity Framework.

Let’s say you have an ASP.Net Core MVC controller action like this in a system that is using Marten for document persistence:

public async Task<IActionResult> CreateItem(
    [FromBody] CreateItemCommand command,
    [FromServices] IDocumentStore martenStore,
    [FromServices] IMessageContext context)
{
    var item = createItem(command);

    using (var session = martenStore.LightweightSession())
    {
        session.Store(item);
        await session.SaveChangesAsync();
    }
    
    var outgoing = new ItemCreated{Id = item.Id};
    
    await context.Send(outgoing);

    return Ok();
}

It’s all contrived, but it’s a relatively common pattern. The HTTP action:

  1. Receives a CreateItemCommand message from the client
  2. Creates a new Item document and persists that with a Marten document session
  3. Broadcasts an ItemCreated event to any known subscribers through Jasper’s IMessageContext service. For the sake of the example, let’s say that under the covers Jasper is publishing the message through RabbitMQ (because I just happened to push Jasper’s RabbitMQ support today).

Let’s say that in this case we need both the document persistence and the message being sent out to either succeed together or both fail together to keep your system and any downstream subscribers consistent. Now, let’s think about all the ways things can go wrong:

  1. If we keep the code the way that it is, what if the transaction succeeds, but the call to context.Send() fails, so we’re inconsistent
  2. If we sent the message before we persisted the document, but the call to session.SaveChangesAsync() failed, we’re inconsistent
  3. The system magically fails and shuts down in between the document getting saved and the outgoing message being completely enqueued — and that’s not that crazy if the system handles a lot of messages

We’ve got a couple options. We can try to use a distributed transaction between the underlying RabbitMQ queue and the Postgresql database, but those can be problematic and are definitely not super performant. We could also use some kind of compensating transaction to reestablish consistency, but that’s just more code to write.

Instead, let’s use Jasper’s support for the “outbox” pattern with Marten:

public async Task<IActionResult> CreateItem(
    [FromBody] CreateItemCommand command,
    [FromServices] IDocumentStore martenStore,
    [FromServices] IMessageContext context)
{
    var item = createItem(command);
    
    using (var session = martenStore.LightweightSession())
    {
        // Directs the message context to hold onto
        // outgoing messages, and persist them 
        // as part of the given Marten document
        // session when it is committed
        await context.EnlistInTransaction(session);
        
        var outgoing = new ItemCreated{Id = item.Id};
        await context.Send(outgoing);
        
        session.Store(item);
        
        await session.SaveChangesAsync();
    }

    return Ok();
}

The key things to know here are:

  • The outgoing messages are persisted in the same Postgresql database as the Item document with a native database transaction.
  • The outgoing messages are not sent to RabbitMQ until the underlying database transaction in the call to session.SaveChangesAsync() succeeds
  • For the sake of performance, the message persistence goes up to Postgresql with all the document operations in one network round trip to the database for just a wee bit of performance optimization.

For more context, here’s a sequence diagram explaining how it works under the covers using Marten’s IDocumentSessionListener:

Handling a Message w_ Unit of Work Middleware (1)

So now, let’s talk about all the things that can go wrong and how the outbox usage makes it better:

  • The transaction fails. No messages will be sent out, so there’s no inconsistency.
  • The transaction succeeds, but the RabbitMQ broker is unreachable. It’s still okay. Jasper has the outgoing messages persisted, and the durable messaging support will continue to retry the outgoing messages when the broker is available again.
  • The transaction succeeds, but the application process is killed before the outgoing message is completely sent to RabbitMQ. Same as the bullet point above.

 

Outbox Pattern inside of Message Handlers

The outbox usage within a message handler for the same CreateItemCommand in its most explicit form might look like this:

public static async Task Handle(
    CreateItemCommand command, 
    IDocumentStore store, 
    IMessageContext context)
{
    var item = createItem(command);


    using (var session = store.LightweightSession())
    {
        await context.EnlistInTransaction(session);

        var outgoing = new ItemCreated{Id = item.Id};
        await context.Send(outgoing);

        session.Store(item);

        await session.SaveChangesAsync();
    }
}

Hopefully, that’s not terrible, but we can drastically simplify this code if you don’t mind some degree of “magic” using Jasper’s cascading message support and Marten transaction middleware:

[MartenTransaction]
public static ItemCreated Handle(
    CreateItemCommand command,
    IDocumentSession session)
{
    var item = createItem(command);

    session.Store(item);
    return new ItemCreated{Id = item.Id};
}

The usage of the [MartenTransaction] attribute directs Jasper to apply a transaction against the IDocumentSession usage and automatically enlists the IMessageContext for the message in that session. The outgoing ItemCreated message returned from the action is sent out through the same IMessageContext object.

 

Jasper Command Line App Support you Wish Your Framework Already Had

Jasper is a new messaging and command runner framework targeting Netstandard2 my shop has been building as a replacement for part of the old FubuMVC framework. I wrote about the general vision and rationale here.

Earlier today I made a v0.7.0 release of Jasper and its related extensions. The pace of development has kicked back up because we’re getting ready to start doing load and chaos testing with our QA folks later this week and we’re already transitioning some smaller, low volume systems to Jasper. The highlights this time are:

  • A lot of optimization for the “cold start” time, especially if you’re using Jasper in combination with ASP.Net Core. I collapsed the ASP.Net Core support back to the core library, so this post is already obsolete.
  • The integration with ASP.Net Core is a lot tighter. For example, Jasper is now using the ASP.Net Core logging under its covers, the ASP.Net Core IHostedService, and just generally plays nicer when used in combination with ASP.Net Core applications.
  • Jasper now has some support for stateful sagas, but only with Marten-backed persistence. I’ll blog about this one soon, and there will be other saga persistence options coming fairly soon. Sql Server backed persistence at a bare minimum.
  • Finer grained control over how certain message types are published
  • Mild improvements to the Marten integration. Again, Jasper isn’t hard coupled to Marten and Postgresql, but it’s just been easy to prove out concepts with Marten first.
  • More command line usages that I’m showing in the rest of this post;)

Command Line Integration

First off, let’s say that you have a simple Jasper application that listens for incoming messages at a designated port configured with this class:

public class SubscriberApp : JasperRegistry
{
    public SubscriberApp()
    {
        // Listen for incoming messages via the
        // built in, socket transport in a 
        // fire and forget way at port 2222
        Transports.LightweightListenerAt(2222);
    }
}

To run your Jasper application as a console application, you can use the Jasper.CommandLine library as a quick helper that also adds some diagnostic commands you may find helpful during both development and deployment time. Using your SubscriberApp class above, you can bootstrap your application in a console application like this:

class Program
{
    static int Main(string[] args)
    {
        return JasperAgent.Run(args);
    }
}

Once that’s done, you can immediately run your application from the command line with dotnet run, which would give you some output like this:

Running service 'SubscriberApp'
Application Assembly: Subscriber, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Hosting environment: Production
Content root path: [the IHostedEnvironment.ContentRootPath value]
Hosted Service: Jasper.Messaging.MessagingActivator
Hosted Service: Jasper.Messaging.NodeRegistration
Listening for loopback messages
Listening for messages at [url]/messages
Listening for messages at [url]/messages/durable

Active sending agent to loopback://replies/
Active sending agent to loopback://retries/
Handles messages:
            [Message Type]: [Handler Type and Handler Method Name]

Now listening on: [listener Uri]
Application started. Press Ctrl+C to shut down.

Other than a little bit of contextual information, it’s about what you would get with the ASP.Net Core command line support. If you’re not familiar with the dotnet cli, you can pass command line arguments to your Program.Main() ​method by using double dashes to separate arguments that apply to dotnet run from the arguments that get passed into your main method. Using the Oakton library for parsing Linux style command arguments and flags, your Jasper application can also respond to other commands and optional flags.

Knowing all that, this:

dotnet run -- -v

or

dotnet run -- --verbose

will run your application with console and debug loggers, and set the minimum log level in the ASP.Net Core logging to “Debug.”

Alternatively, you can also override the log level by:

dotnet run -- --log-level Information

or

dotnet run -- -l Trace

where the value is one of the values in the LogLevel enumeration.

To override the environment your application is running under, you can use this flag:

dotnet run -- --environment Development

or use the “-e” short version of that.

So what, what else do you got?

You can run a Jasper application, but there’s actually quite a bit more. If you type dotnet run -- ?, you can see the other available commands:

 

Screen Shot 2018-04-11 at 3.53.09 PM

The “export-json-schema” and “generate-message-types” commands are from an extension library that allows you to export JSON schema documents for the known message types or generate C# classes with the necessary Jasper message type identity from JSON schema documents. The command line support is extensible, allowing you to add prepackaged commands from addon Nugets or even be exposed from your own application. I’m going to leave that to a later post or *gasp*, updated documentation.

Preview the Generated Code

If you read my earlier post on Jasper’s Roslyn-Powered “Special Sauce,” you know that Jasper internally generates and compiles glue code to handle messages or HTTP requests. To help troubleshoot applications or just to understand the interplay between message handlers and any configured middleware, you can use this command to either list out the generated code or export it to a file:

dotnet run -- code -f export.cs

 

Check out the IoC Container Configuration

As a long time IoC tool author and user, I’m painfully aware that people run into issues with service registrations being incorrect or using erroneous lifecycles. To help ease those issues, Jasper allows you to see the service registrations of your entire application with this command:

dotnet run -- services

This is just displaying the output of the Lamar WhatDoIHave() report, similar to StructureMap’s WhatDoIHave() functionality.

Validate the System

As part of deployment or maybe even local development, you can choose to just start up the application, run all the registered environment checks, and verify that all the known message handlers and HTTP routes can be successfully compiled — with oodles of ugly red textual output if any check along the way fails. That’s done with dotnet run -- validate.

 

Manage Subscriptions

It’s admittedly kind of boring and I’m running out of time before I need to head home, but there is a dotnet run -- subscriptions command that you can use to manage message subscriptions at deployment or build time that’s documented here.

 

Next up:

I’ll get a decent, business facing example of Jasper’s stateful saga support.

 

The Marten Webinar is Online, and Answering Some Questions

JetBrains was gracious enough to let me record an introductory webinar last week on the Marten project that lets .Net developers successfully treat Postgresql as a fully ACID-compliant, document database and event store. The recording posted this morning and there’s a link to it right below:

Questions from the Webinar

These are some of the leftover questions we weren’t able to get to during the webinar:

Is there an ability to do faceted searches in Marten?

I don’t know why, but I just couldn’t make out the word “faceted” during the talk and this one slipped through.

In Marten itself, no, but we’re sitting on top of Postgresql that does support faceted searching. In the longer run, we’ve talked about having some easy to use facilities that allow you to either “project” a Marten-persisted document type to a flat database view or to possible write a “read side” table for reporting during document writes. My attitude on these kinds of features is to try to lean on Postgresql wherever possible to try to keep Marten’s already very large feature set and codebase from getting (more) bloated.

Have you any experience running Marten against Citus?

No, but I’d be very curious to see how that goes. I’ve purposely put off any kind of software based sharding with Marten in hopes that Citus just works. Volunteering? 😉

How did you convince your company to build marten from the ground up instead of using existing docdb?

Marten was originally conceived of and executed as a near drop in replacement for our existing document database (RavenDb) that was causing us quite a few production issues. At the time, we theorized that it would be easier to build an in place replacement for RavenDb than to convert a massive, existing project to some completely different database and persistence framework. We were a very OSS-friendly shop at the time, and Marten was actually my then manager’s concept.

Can you extract values from the json in explicit table fields?

Marten can only work against tables that it controls with an expected structure, so no, sorry.

Can we use map-reduce queries like in RavenDB? And is there async index creation, with map-reduce?

Indexes are just Postgresql indexes, even when calculated against a JSON search. We don’t directly support map-reduce, and I don’t actually think we’ll need to in the long run. See the section on faceted search above too.

Will you be posting the code used in the webinar somewhere?

Yep, it’s in GitHub here.

Successfully Running an xUnit Suite in Parallel

TL:DR: Don’t call Task.Wait() in your xUnit tests if you want things running faster and in parallel. In other words, async turtles all the way down. This is a requested post from my buddy Jim Holmes.

In my recent OSS efforts like Marten, Jasper, and Lamar, I have tended to lean much more heavily on top down integration tests than having lots of intermediate and low level unit tests. Putting aside the wisdom of that approach aside for another time, depending so much on integration testing has made the main testing suite in Jasper run too slowly for my comfort as the project has grown.

Like many xUnit users, the second I hit issues with test suites locking up or failing unpredictably, I lazily slap on the directive to prevent parallel test execution like so:

using Xunit;

[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]

As I said, the Jasper suite got too slow for productive, quick twitch development, so I finally broke down and committed enough time to eliminate all the issues in the Jasper code and test suite that was stopping us from running the tests in parallel. It’s a huge, squashed commit, but you can see what I did here.

In a few places, I was using static members to record actions during integration tests as just a mechanically cheap way of asserting correct behavior. I had to move all of that to object instances that were scoped to the test run. Not that big of a deal.

The bigger problem by far was deadlock issues in bootstrapping a Jasper application, which was kind of a problem where there are ~200 tests that each try to bootstrap a Jasper application as part of the test. To optimize the “cold start” time of Jasper, I heavily parallelize startup activities through Task objects. The synchronous version of bootstrapping has to eventually do a couple Task.GetAwaiter().GetResult() calls (once in Jasper, once in Lamar where it uses StructureMap’s old trick of parallelizing the type scanning), and that was prone to deadlocks.

The original pattern of many integration tests looked like this:

[Fact]
public void some_name()
{
    using (var runtime = JasperRuntime.For(_ => { // some configuration}))
        // do stuff and run assertions
    });
}

After re-plumbing the bootstrapping and adding purely asynchronous bootstrapping and teardown methods to both Jasper and the underlying Lamar IoC container, I mostly moved to this pattern instead:

[Fact]
public async Task some_name()
{
    var runtime = await JasperRuntime.ForAsync(_ => {
        // some configuration
    });

    try {
        // do stuff and run assertions
    }
    finally {
        // shutdown the running app w/ 100% async API calls
        await runtime.Shutdown();
    }
}

Long story short, it sucked, but now the tests can happily run in parallel and it made a huge difference in the test suite runtime and I’ve been able to be much more productive.

Lamar v0.9 — Decorators and “MVP”

Lamar, which started life as “BlueMilk,” is the now permanent codename for my next generation IoC tool that is meant to replace StructureMap in new .Net Core projects. I did a little bit of work this morning to add in at least the basic functionality for decorators similar to what StructureMap has supported since 3.0 (but no other kinds of interception for now):

[Fact]
public void decorator_example()
{
    var container = new Container(_ =>
    {
        // This usage adds the WidgetHolder as a decorator
        // on all IWidget registrations
        _.For<IWidget>().DecorateAllWith<WidgetHolder>();
        
        // The AWidget type will be decorated w/ 
        // WidgetHolder when you resolve it from the container
        _.For<IWidget>().Use<AWidget>();
        
        _.For<IThing>().Use<Thing>();
    });

    // Just proving that it actually works;)
    container.GetInstance<IWidget>()
        .ShouldBeOfType<WidgetHolder>()
        .Inner.ShouldBeOfType<AWidget>();
}

This change, and a few other miscellaneous fixes for integration with Jasper, are available on Nuget in Lamar 0.9.0. At this point, I feel like Lamar is already at the feature set I intend to support for a 1.0 release. It’s going to at least go through some load testing at my work over the next month, and still lacks documentation (but most of it behaves identically to StructureMap or the ASP.Net Core DI compliance). All the same,  I’m declaring it ready for real usage if anybody is up for trying it out and hopefully sharing any feedback. It should *mostly* be a drop in replacement for StructureMap if you’re not doing too much weird stuff.

For more information, see some of my previous blog posts about Lamar/BlueMilk:

 

Integrating Jasper into ASP.Net Core

Continuing a blog series on Jasper functionality:

  1. Jasper’s Configuration Story 
  2. Jasper’s Extension Model
  3. Integrating Marten into Jasper Applications
  4. Durable Messaging in Jasper 
  5. Integrating Jasper into ASP.Net Core (this one)
  6. Jasper’s HTTP Transport
  7. Jasper’s “Outbox” Support within ASP.Net Core Applications

There will be some need for completely headless services written with Jasper that rely strictly on TCP connections or yet to come queueing transports, but I expect that most of the systems at work where we’ll use Jasper will be within ASP.Net Core applications.

Moreover, as a nasty lesson learned from my hubristic attempts at creating a freestanding development ecosystem with FubuMVC, Jasper is meant to be merely a good citizen within the greater server side ASP.Net Core ecosystem. In regards to this blog post, that means using as much of the standard Hosting model as possible. For example, Jasper supports the IHostedService model from ASP.Net Core out of the box for long running background services or startup and shutdown actions.

As of Jasper 0.6, I pulled the HTTP support and ASP.Net Core integration into a separate Jasper.Http Nuget. This might feel like the tail wagging the dog, but I really only did this to optimize the core Jasper testing suite because bootstrapping ASP.Net Core on every integration test was slowing the automated build down too much. If I can find a way to optimize or at least parallelize much more of the bootstrapping with the messaging, I will consider merging things back together again later.

When Jasper is integrated into an ASP.Net Core system, it:

  • Adds more service registrations to the application
  • Bootstraps the JasperRuntime object and places that within the container so that the Jasper transports will be cleanly shut down when the IWebHost is disposed
  • Replaces the built in DI container with Lamar (Jasper only works with Lamar at this point)
  • Jasper also sneaks in some ASP.Net Core middleware to add its own routes into the application, which I’ll show off in the next post about Jasper’s HTTP messaging transport

All of this is documented in the Jasper Getting Started page and in the specific documentation for ASP.Net Core integration.

Longer term, I might try to move Jasper closer to the existing ASP.Net Core bootstrapping mechanisms.

Bootstrapping ASP.Net Core the Idiomatic Jasper Way

The first option is really about adding HTTP support to an idiomatic Jasper application. In this case, you just use the JasperHttpRegistry from the Jasper.Http library as the base class for your application definition like so:

public class AppWithMiddleware : JasperHttpRegistry
{
    public AppWithMiddleware()
    {
        // Do the normal stuff you do to configure
        // service registrations, configuration, and
        // messaging support

        Http.Configure(app =>
        {
            app.UseMiddleware<CustomMiddleware>();

            // Explicitly control the order in which the Jasper
            // middleware is placed within the ASP.Net Core
            // pipeline. 
            app.AddJasper();

            // Just to show how you can configure ASP.Net Core
            // middleware that runs after Jasper's RequestDelegate,
            // but do note that Jasper has its own default "not found"
            // behavior
            app.Run(c =>
            {
                c.Response.StatusCode = 404;

                return c.Response.WriteAsync("Not found");
            });
        });
    }
}

A couple things to note:

  • The Http property in the class shown above is just the IWebHostBuilder interface you’re already used to if you use ASP.Net Core today
  • If the call to IApplicationBuilder.AddJasper() is omitted, Jasper will add its own middleware to the very end of the pipeline
  • The HTTP bootstrapping in the idiomatic model is somewhat parallelized with the messaging support bootstrapping
  • I’d argue that this usage makes the ASP.Net Core StartUp conventional configuration model unnecessary, but you’re perfectly able to continue using that if you want.

I hope to do more optimizations to the cold startup time in the future for the idiomatic Jasper approach that would make this option be more attractive. Right now, the biggest reason to use this approach over the following is to be able to use Jasper’s console application harness and Storyteller integration.

 

Adding Jasper to an Existing ASP.Net Core System

You can also add Jasper to an existing ASP.Net Core system using its idiomatic bootstrapping approach. In this case, you still start with the JasperHttpRegistry base class from the Jasper.Http library, but you mostly use this to configure the messaging support:

public class SimpleJasperBusApp : JasperHttpRegistry
{
    public SimpleJasperBusApp()
    {
        // Enable the HTTP messaging transport
        Http.Transport.EnableListening(true);
        
        // Listen for TCP messages at port 2222
        Transports.LightweightListenerAt(2222);
    }
}

Then, to add the Jasper support to your ASP.Net Core application, you would add these calls:

var builder = new WebHostBuilder();
builder
    .UseKestrel()
    .UseUrls("http://localhost:3003")
    .UseStartup<Startup>()
    
    // This *has* to be the last call 
    // to your IWebHostBuilder
    .UseJasper<SimpleJasperBusApp>();


theHost = builder.Build();

theHost.Start();

I hate this from a usability perspective, but for right now, the call to UseJasper() has to be added after any other IStartUp registration including the UseStartup<T>() method. You still have the same ability to explicitly control the order of the Jasper middleware within your ASP.Net Core middleware pipeline.