Using Autofac dependency injection with Azure Worker Role

A project I am working on recently required me to set up dependency injection using Autofac for a Microsoft Azure Cloud Service Worker Role.

Some background

The SaaS project I am currently working on requires occasional heavy operations to be performed based on some of the user interaction. An example is the re-calculation of a notification schedule when the user changes the notification rules.

As I don't want to keep my users waiting, and since I use Azure, these operations are performed in the background using the common Azure pattern of triggering an action from a web role and merely queueing a message for a background worker role to have that do the heavy lifting.

Azure Web and Worker Role connected with ServiceBus queue

The worker role will rely on exactly the same domain model and functionality that the web role does, which means I need my worker role to play nice with Autofac, as this is what the rest of the application uses for DI.

What I want to achieve is basically this:

  1. I want to use Autofac in my worker role
  2. I want it to be configured in one location, and not seep into the role
  3. Also: I want my worker role to handle multiple isolated jobs

Using the RoleEntryPoint for setup

A plain vanilla worker role will look something like this.

namespace AwesomeApp
{
    public class WorkerRole : RoleEntryPoint
    {
        public override void Run()
        {
            // Do the magic!
            // Maybe read from a queue and do some processing..
        }

        public override bool OnStart()
        {
            // Configure your role
            return base.OnStart();
        }

        public override void OnStop()
        {
            // Tear the role down
            base.OnStop();
        }
    }
}

But rather than doing that, I will use the RoleEntryPoint for configuring Autofac and initiating jobs.

A "job" in this case is an implementation of an abstract class that exposes the methods Run() and Stop(). The idea is to mimick the design of the RoleEntryPoint, but simply move it one stop away from the RoleEntryPoint, so we can use that strictly for wiring everything up.

namespace AwesomeApp
{
    public class HeavyLifter : QueueJob
    {
        private readonly QueueClient _client;
        private readonly ISomeDependency _dependency;
        private readonly ManualResetEvent _completedEvent = new ManualResetEvent(false);

        public HeavyLifter(QueueClient client, ISomeDependency dependency)
        {
            _client = client;
            _dependency = dependency;
        }

        public override void Run()
        {
            _client.OnMessage((receivedMessage) => { 
                // Handle the message and do some heavy liftin'
                _dependency.LiftHeavy();
            });

            _completedEvent.WaitOne();
        }

        public override void Stop()
        {
            _client.Close();
            _completedEvent.Set();
        }
    }
}

The job is using two injected dependencies. They may in turn be using other dependencies. In order for that to work we need to wire things up in the RoleEntryPoint.

namespace AwesomeApp
{
    public class WorkerRole : RoleEntryPoint
    {
        private IContainer _container;
        private HeavyLifter _heavy;

        public override void Run()
        {
            var jobs = new List<Task>();

            _heavy = _container.Resolve<HeavyLifter>();
            jobs.Add(Task.Factory.StartNew(() => _heavy.Run()));

            // .. Start more jobs if you need

            Task.WaitAll(jobs.ToArray());
        }

        public override bool OnStart()
        {
            ConfigureDependencyInjection();

            return base.OnStart();
        }

        public override void OnStop()
        {
            _heavy.Stop();
            base.OnStop();
        }

        private void ConfigureDependencyInjection()
        {
            // Autofac configuration
            var builder = new ContainerBuilder();

            // Register your dependencies
            builder.RegisterType<ISomeDependency>().AsImplementedInterfaces();

            // Register the jobs
            builder.RegisterType<HeavyLifter>().AsSelf() 

            _container = builder.Build();
        }
    }
}

And there we are

When using Autofac to resolve the jobs in the Run()-method of RoleEntryPoint, all the dependencies required within that job will be handled by Autofac.

So now Autofac is configured only in the RoleEntryPoint, my code gets hooked up with the required dependencies and I can get back to implementing my worker role.

View Comments