Introducing SaasKit - Multi-tenancy made easy

In its simplest terms, multi-tenancy refers to running a single instance of software (e.g. a web application) for multiple tenants/customers.

There are many benefits to using a multi-tenant architecture, for example, a better deployment story (you have just one instance to deploy for all tenants) and improved resource utilization.

It does however introduce an additional layer of complexity and building your first multi-tenant application can be a little daunting.

Over the last few years I've worked on a number of multi-tenant applications. These have ranged both in complexity and platform. I've developed multi-tenant applications using ASP.NET MVC and Web API and also integrated with various back-end messaging systems. As such this is a subject that is not only close to my heart but also one that had very little guidance in the .NET space.

For this reason over the next few months I will be putting my time into a new project called SaasKit - a .NET toolkit for building SAAS (Software As A Service) applications.

The goal of the project is to help developers build SAAS applications without getting in the way. It aims to be platform agnostic and as simple to use as possible. Since the two are closely aligned, one key feature of SaasKit is enabling multi-tenancy.

Why now?

This is something I've wanted to do for a while but have struggled to find a solution that will work for multiple frameworks. That was of course until the release of OWIN and Microsoft's implementation of the OWIN specification, Katana.

OWIN aims to decouple web applications from the environments/servers that run them. This means that we can write OWIN Middleware Components (OMC) that will work for any framework that supports OWIN. Furthermore, you can host these frameworks (Nancy, Web API, SignalR to name a few) together within the same application.

Tenant Identification

Although it's a broad subject, this post covers what I see as perhaps the most fundamental aspect of multi-tenancy in web applications, Tenant Identification; the process of identifying a tenant based on some aspect of the request and making the tenant instance available to the consuming application.

The code below is very much alpha's-distant-cousin but I hope to get something up on GitHub in the next few weeks.

Resolving the current tenant is a two step process:

  1. Getting the information from the incoming request used to resolve a tenant e.g. the hostname.
  2. Resolving the hostname based on the provided identifier.

Whilst you could argue that in most cases the identifier will be some part of the hostname I want SaasKit to be as flexible as possible. You may actually identify your tenant by client IP address, port number or request header.

SaasKit provides a simple delegate for tenant identification strategies:

public delegate string TenantIdentificationStrategy(IOwinRequest request);

I'm using the Microsoft.Owin library to provide strongly typed access to the OWIN environment, request and response objects, rather than passing around IDictionary<string, object>. This may change (if there's any major performance overhead) but it certainly makes OWIN easier to work with.

We'll include some common strategies in the library but of course you're free to define your own.

public static class TenantIdentificationStrategies
{
    public static TenantIdentificationStrategy FromHostname
    {
        get
        {
            return req => req.Uri.Host;
        }
    }
}

To resolve a tenant we have another simple interface:

public interface ITenantResolver
{
    Task<object> Resolve(string identifier);
}

The resolver takes the string identifier returned from the appropriate identification strategy and resolves the tenant. Currently there is no constraint on what you return (since I expect people will store lots of different information about their tenants) but I may need to require an interface to achieve some of the other goals of the project.

The other reason for splitting up the identification strategy and tenant resolution is that we don't actually want to resolve the tenant on every request. Suppose we have an ITenantResolver that loads tenant information from a database (a very common scenario), it would not be very performant if we did this on every request. Instead we will use the MultiTenantEngine to keep track of running tenant instances.

The MultiTenantEngine implements the following interface:

public interface IMultiTenantEngine
{
    Task BeginRequest(IOwinContext context);
    Task EndRequest(IOwinContext context);
}

The actual implementation takes care of storing/caching the running tenant instances and adding the matched tenant to the OWIN environment at the beginning of the request so that it is available to other middleware:

public class MultiTenantEngine : IMultiTenantEngine
{
    private const string CacheName = "SaasKit.MultiTenantEngine";

    private readonly TenantIdentificationStrategy identificationStrategy;
    private readonly ITenantResolver tenantResolver;
    private readonly MemoryCache runningInstances;

    public MultiTenantEngine(TenantIdentificationStrategy identificationStrategy, ITenantResolver tenantResolver)
    {
        this.identificationStrategy = identificationStrategy;
        this.tenantResolver = tenantResolver;
        this.runningInstances = new MemoryCache(CacheName);
    }

    public async Task BeginRequest(IOwinContext context)
    {
        Console.WriteLine("Host begin request.");

        var instance = await GetTenantInstance(context.Request);

        if (instance != null)
        {
            context.Set(Constants.OwinCurrentTenant, instance);
        }            
    }

    public Task EndRequest(IOwinContext context)
    {
        Console.WriteLine("Host end request.");
        return Task.FromResult(0);
    }

    private async Task<TenantInstance> GetTenantInstance(IOwinRequest request)
    {
        var identifier = identificationStrategy.Invoke(request);

        if (identifier == null)
        {
            throw new ArgumentException("The identification strategy did not return an identifier for the request.");
        }

        var instance = runningInstances.Get(identifier) as TenantInstance;

        if (instance != null)
        {
            Console.WriteLine("Tenant instance already running.");
            return instance;
        }


        Console.WriteLine("Tenant instance not found. Resolving tenant.");

        var tenant = await tenantResolver.Resolve(identifier);

        if (tenant != null)
        {
            Console.WriteLine("Tenant found with identifier {0}", identifier);
            Console.WriteLine("Starting Tenant Instance");

            instance = new TenantInstance(tenant);

            var policy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.UtcNow.AddHours(6) };
            runningInstances.Set(new CacheItem(identifier, instance), policy);
        }

        return instance;
    }
}

The meat of the logic is in the GetTenantInstance method. Here we get the identifier for the current request using the provided TenantIdentificationStrategy. This is used as a cache item key for the running instances. If the tenant instance already exists in the cache we can simply return it, otherwise we resolve the tenant using the provided ITenantResolver, cache and return it.

Finally we add the current TenantInstance to the OWIN environment making it available to other middleware (such as Nancy or ASP.NET Web API).

You may wonder why I've used MemoryCache to store the running instances rather than something like ConcurrentDictionary. The reason is that MemoryCache allows us to set a policy for expiring cached items. This means we can implement sliding expiration and optionally perform cleanup for a tenant after a certain period of inactivity. This will be important when we start supporting other high level concepts of multi-tenancy.

Finally the piece that wires the engine up to our OWIN compatible host is a piece of OWIN middleware, aptly named MultiTenantMiddleware:

public class MultiTenantMiddleware : OwinMiddleware
{
    private readonly IMultiTenantEngine engine;

    public MultiTenantMiddleware(OwinMiddleware next, IMultiTenantEngine engine)
        : base(next)
    {
        if (engine == null)
        {
            throw new ArgumentNullException("engine");
        }

        this.engine = engine;
    }

    public async override Task Invoke(IOwinContext context)
    {
        await engine.BeginRequest(context);
        await Next.Invoke(context);
        await engine.EndRequest(context);
    }
}

This class takes care of invoking the appropriate method on the IMultiTenantEngine at the beginning and end of the request.

To register the middleware with the OWIN IAppBuilder there is a convenient helper method:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var engine = new MultiTenantEngine(
            TenantIdentificationStrategies.FromHostname,
            new MyResolver());

        app.UseMultiTenancy(engine); // <-- this one
    }
}

Of course, this isn't much use by itself. We really need some other middleware, so let's wire up Nancy.

Using Nancy

I'm not going to go into detail on exactly how you use Nancy with OWIN but after pulling in the required NuGet packages it's really just a case of doing the following in your startup class:

   app.UseMultiTenancy(engine)
      .UseNancy();

SaasKit will provide separate libraries for framework specific integration. In the case of Nancy we just have an extension on NancyContext that grabs the current tenant from the OWIN environment. So we can see this in action I'll just create a simple module that outputs the current tenant:

public class HomeModule : NancyModule
{       
    public HomeModule()
    {
        Get["/"] = _ =>
        {
            var currentTenant = Context.GetCurrentTenant<object>();
            return "Current Tenant: " + currentTenant;
        };
    }
}

In my demo application I just have a ITenantResolver that always returns the same tenant:

public class MyResolver : ITenantResolver
{
    public Task<object> Resolve(string identifier)
    {
        var tenant = "Tenant1";
        return Task.FromResult<object>(tenant);
    }
}

I'm also using the Katana HttpListener host within a console application:

class Program
{
    static void Main(string[] args)
    {
        var url = "http://localhost:8000";

        using (WebApp.Start<Startup>(url))
        {
            Console.WriteLine("Running on {0}", url);
            Console.WriteLine("Press enter to exit");
            Console.ReadLine();
        }
    }
}

Making a request to http://localhost:8000/ diplays (as you may have guessed):

Current Tenant: Tenant 1

Admittedly this is not the most useful of demos. However, with the current tenant instance being available in the NancyContext you can now do tenant specific things within your application.

To implement tenant specific themes I'm going to add a custom view location convention in Nancy. Since we have access to NancyContext we can use this as part of the view location path e.g. a for Tenant1 we'll look for views in Views/Tenant1/. To do this I'll just add my own Nancy bootstrapper:

public class Bootstrapper : DefaultNancyBootstrapper
{
    protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
    {
        base.ApplicationStartup(container, pipelines);

        this.Conventions.ViewLocationConventions.Add((viewName, model, context) =>
        {
            var tenant = context.Context.GetCurrentTenant<object>();
            return string.Concat("views/", tenant, "/", viewName);
        });
    }
}

I'll also update my Nancy module to return a view:

   public HomeModule()
   {
       Get["/"] = _ =>
       {
           var model = new
           {
               Tenant = Context.GetCurrentTenant<object>()
           };

           return View["home", model];
       };
   }

I then created views /views/tenant1/home.html and /views/tenant2/home.html each with a different background.

My tenant resolver was then updated to return a different tenant based on the hostname:

public class MyResolver : ITenantResolver
{
    public Task<object> Resolve(string identifier)
    {
        var tenant = identifier == "localhost" ? "Tenant1" : "Tenant2";
        return Task.FromResult<object>(tenant);
    }
}

I also updated my server to listen on multiple URLs (I've mapped dev.local in my hosts file):

   static void Main(string[] args)
   {
       var startOptions = new StartOptions();
       startOptions.Urls.Add("http://localhost:8000");
       startOptions.Urls.Add("http://dev.local:8000");

       using (WebApp.Start<Startup>(startOptions))
       {
           Console.WriteLine("Running on {0}", string.Join(" and ", startOptions.Urls.ToArray()));
           Console.WriteLine("Press enter to exit");
           Console.ReadLine();
       }
   }

Finally, the thing you were all waiting for, if I run the server and make a request to http://localhost:8000 and http://dev.local:8000 respectively I get the following:

Nancy Multi-tenancy

Whilst this was a simplistic example I hope you can see the benefits of SaasKit. If you would like to contribute to SaasKit or have ideas on what we should do next, please use GitHub issues.

In my next post I'll cover how to achieve the same thing in ASP.NET Web API and MVC.


Ben Foster

About Me

I'm a software engineer and aspiring entrepreneur with 12+ years experience in the tech industry and have worked with startups and SMB’s in areas such as healthcare, recruitment and e-commerce (I even worked in enterprise, once). I founded my first startup Fabrik in 2011.

I now head up the engineering team at Checkout.com. If you're interested in working in an exciting fin-tech company, drop me a message on Twitter.

Creative Commons Licence