Using ActiveMQ with Optimizely 12

Whilst setting up Optimizely 12 to work with RabbitMQ is rather straight forward, making it use ActiveMQ for transport instead requires a bit more configuration on our part. Here is a short article on how to set up Optimizely 12 to use MassTransit (EPiServer.Events.MassTransit) with ActiveMQ.

For a short instruction on how to set up a local ActiveMQ server, please see Muthu Kumaran‘s article on How to use ActiveMQ in C#.

Settings up Optimizely to use ActiveMQ requires a bit of settings. Here is an example on what you can add to your appsettings.Development.json. Since Optimizely will be setting up the queues and topics that it needs automatically, it will require the permission to do so. The default admin user may suffice for a local test, but depending on your risk assesment you may want to limit what the user you use in other environments will be able to do.

{
  "ActiveMQ": {
    "Port": 61616,
    "Host": "localhost",
    "Username": "admin",
    "Password": "admin",
    "ExchangeName": "VirtualTopic.EPiServer.Events.EventMessage",
    "PrefetchCount": 100,
    "Enabled": true
  },

To make the startup file less cluttered you probably already have some sort of extension files for dividing up your initialization. Here is the ActiveMQ related code from what one relating to Optimizely may look like.

Just like the offered AddRabbitMqTransport method, we need to create our own AddActiveMqTransport and use it while setting up the mass transit event provider.

public static class OptimizelyExtensions
{
  public static IServiceCollection ConfigureOptimizely(this IServiceCollection services, IConfiguration configuration)
  {
    // ...

    if (bool.Parse(configuration.GetSection("ActiveMQ:Enabled").Value))
    {
      _ = services.AddMassTransitEventProvider(null, x => x.AddActiveMqTransport(configuration));
    }

    // ...

    return services;
  }

The AddActiveMqTransport method uses the configuration to add and configure both the producing and the consumption of messages. Of course, here you can make changes and alterations if you need to.

  private static void AddActiveMqTransport(this IBusRegistrationConfigurator busRegistrationConfigurator, IConfiguration configuration)
  {
    _ = busRegistrationConfigurator.AddConsumer<SiteEventsConsumer>();

    busRegistrationConfigurator.UsingActiveMq(delegate (IBusRegistrationContext context, IActiveMqBusFactoryConfigurator cfg)
    {
      string host = configuration.GetSection($"ActiveMQ:Host").Value;
      int port = int.Parse(configuration.GetSection($"ActiveMQ:Port").Value, CultureInfo.InvariantCulture);
      string username = configuration.GetSection($"ActiveMQ:Username").Value;
      string password = configuration.GetSection($"ActiveMQ:Password").Value;
      string exchangeName = configuration.GetSection($"ActiveMQ:ExchangeName").Value;
      int prefetchCount = int.Parse(configuration.GetSection($"ActiveMQ:PrefetchCount").Value, CultureInfo.InvariantCulture);

      cfg.Host(host, port, h =>
      {
        h.Username(username);
        h.Password(password);
      });
      cfg.Message(delegate (IMessageTopologyConfigurator<EventMessage> x)
      {
        x.SetEntityName(exchangeName);
      });
      cfg.PublishTopology.VirtualTopicPrefix = string.Empty;
      cfg.ReceiveEndpoint(new TemporaryEndpointDefinition(tag: null, concurrentMessageLimit: null, prefetchCount), delegate (IActiveMqReceiveEndpointConfigurator e)
      {
        e.ConfigureConsumeTopology = false;
        e.Consumer(() => context.GetService<SiteEventsConsumer>());
        e.UseDataContractBinarySerializer(context.GetService<DataContractBinarySerializer>());
        e.AutoDelete = true;
        e.PrefetchCount = prefetchCount;
        e.Durable = false;
        e.Bind(exchangeName, delegate (ITopicBindingConfigurator x)
        {
          x.Durable = false;
          x.AutoDelete = true;
        });
        e.ConfigureConsumer<SiteEventsConsumer>(context);
      });
    });
  }
}

And finally, just a short example on how you can use the above extension method in your Startup class.

public class Startup
{
  private readonly IConfiguration _configuration;

  public Startup(IConfiguration configuration)
  {
    _configuration = configuration;
  }

  public void ConfigureServices(IServiceCollection services)
  {
    // ...
    _ = services.ConfigureOptimizely(_configuration);

An easy way to try this is to just set up two URLs to the same Optimizely 12 website locally and point it to the ActiveMQ server. You can then publish changes in one of the websites and see that the change immediatly appears at the other. Logging into the ActiveMQ interface you can also see the messages being sent and consumed by your two websites.