Jasper’s Extension Model

Continuing a new blog series on Jasper:

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

 

 

The starting point of any Jasper application is the JasperRegistry class that defines the configuration sources, various settings, and service registrations, similar in many respects to the IWebHostBuilder and Startup types you may be familiar with in ASP.Net Core applications (or the FubuRegistry for any old FubuMVC hands). A sample one is shown below:

public class SubscriberApp : JasperRegistry
{
    public SubscriberApp()
    {
        Subscribe.At("http://loadbalancer/messages");
        Subscribe.ToAllMessages();

        Transports.LightweightListenerAt(22222);
    }
}

If everything you can possibly change or configure is done by the internal DSL exposed by the JasperRegistry class, it’s only natural then that the extension model is just this:

public interface IJasperExtension
{
    void Configure(JasperRegistry registry);
}

As a sample, here’s an available extension from the Jasper.Marten library that lets you opt into using Marten-backed message persistence that will be featured in a blog post later in this series:

/// <summary>
/// Opts into using Marten as the backing message store
/// </summary>
public class MartenBackedPersistence : IJasperExtension
{
    public void Configure(JasperRegistry registry)
    {
        // Override an OOTB service in Jasper
        registry.Services.AddSingleton<IPersistence, MartenBackedMessagePersistence>();

        // Jasper works *with* ASP.Net Core, even without a web server,
        // so you can use their IHostedService model for long running tasks
        registry.Services.AddSingleton<IHostedService, SchedulingAgent>();

        // Customizes the Marten integration a little bit with
        // some custom schema objects this extension needs
        registry.Settings.ConfigureMarten(options =>
        {
            options.Storage.Add<PostgresqlEnvelopeStorage>();
        });
    }
}

To apply and use these extensions, you have two options. First, you can say that the exposed extension has to be explicitly added by the application developers in their JasperRegistry with syntax like this:

public class ItemSender : JasperRegistry
{
    public ItemSender()
    {
        Include<MartenBackedPersistence>();
        // and a bunch of other stuff that isn't germane here
    }
}

The other option is to make an extension be auto-discovered and applied whenever the containing assembly is part of the application. To do this, add an assembly level attribute to your extension library that references the extension type you want auto-loaded. Here’s an example from the Jasper.Consul extension library:

// This is a slight change from [FubuModule] in FubuMVC
// Telling Jasper what the extension type is just saves the
// type scanning discovery. Got that idea from a conversation
// w/ one of the ASP.Net team members
[assembly:JasperModule(typeof(ConsulExtension))]

namespace Jasper.Consul.Internal
{
    public class ConsulExtension : IJasperExtension
    {
        public void Configure(JasperRegistry registry)
        {
            registry.Services.For<IUriLookup>().Add<ConsulUriLookup>();
            registry.Services.For<IConsulGateway>().Add<ConsulGateway>();
        }
    }
}

When to use either model? I’m a big fan of the “it should just work” model and the auto-discovery in many places, but plenty of other folks will prefer the explicit Include<Extension>() call in their JasperRegistry to make the code be more self-documenting.

In either case, there’s a little bit of trickery going on behind the scenes to order both the service registrations and any changes to “Settings” objects to create a precedence order like this:

  1. Application specific declarations in the JasperRegistry class — regardless of the ordering of where the Include() statements wind up
  2. Extension declarations
  3. Core framework defaults

 

A Little History about the Ideas Here

When Chad Myers and I started talking through the ideas that later became FubuMVC, one of our goals was to maximize our ability to apply customer-specific extensions and customizations to the on premises model application we were building at the time. We knew that many a software shop had crashed and burned in that situation if they resorted to using customer specific forks of their core product. We envisioned a web framework that would pretty well let you add or change almost anything in customer extensions without forcing any kind of fork to the core application. The Open-Closed Principle taken to an extreme, if you will. Using FubuMVC extensions (we originally called them “Bottles”), you could add all new routes, change IoC service registrations, swap out views, and even inject content into existing views in the core application with a model where you just drop the extension assembly into the bin path of the application and go.

I want to say that it’s one of the cleverest things I’ve ever successfully completed, and it definitely added some value (not so much in the app it was meant for because they ditched the on premises model shortly after and succeeded without the crazy extensibility;)) All that said though, Jasper is meant for a different world where we might not be quite so eager to build large applications and I’m much more gun shy about complexity in my OSS projects than 10 years younger me was, so the extensibility will not be quite so big a part of Jasper’s core identity and philosophy as it was in the FubuMVC days.

 

One thought on “Jasper’s Extension Model

Leave a comment