Multi-tenancy on S#arp Architecture

January 11, 2011

Update: the code below is out of date, please see Multi-tenancy on S#arp Architecture Revisited for a better solution

Recently I’ve been working on adding multi-tenancy to a web application based on the excellent S#arp Architecture and thought I’d share what I have so far.

This implementation is mainly based on this post from Robert Johnson, and uses a separate database for each tenant, along with a master database to hold the details of the tenants (and anything else you wish). The main drawback with using separate databases is handling the migrations, I haven’t yet got a solution to this but will look at a way of automating it using something like Migrator.Net in a future post.

All of the domain entities inherit from SharpArch.Core.DomainModel.Entity, and I use a marker interface to indicate which entities are part of the tenant domain model.

namespace SharpArchitecture.MultiTenant.Core.Contracts
{
  /// <summary>
  /// Marker interface for multi tenant entities.
  /// </summary>
  public interface IMultiTenantEntity { }
}

We can now create our domain entities for the tenants:

using NHibernate.Validator.Constraints;
using SharpArch.Core.DomainModel;
using SharpArchitecture.MultiTenant.Core.Contracts;

namespace SharpArchitecture.MultiTenant.Core
{
  public class Customer : Entity, IMultiTenantEntity
  {
    [DomainSignature]
    [NotNullNotEmpty]
    public virtual string Code { get; set; }

    [DomainSignature]
    [NotNullNotEmpty]
    public virtual string Name { get; set; }
  }
}

And we create an entity, to be stored in the master database, to represent each tenant:

using SharpArch.Core.DomainModel;

namespace SharpArchitecture.MultiTenant.Core
{
  public class Tenant : Entity
  {
    public virtual string Name { get; set; }

    [DomainSignature]
    public virtual string Domain { get; set; }

    public virtual string ConnectionString { get; set; }
  }
}

The application will make use of a separate NHibernate session for each tenant, identified by a key. For the master database the default session will be used. So, we create an interface that will provide access to the key:

namespace SharpArchitecture.MultiTenant.Framework.Services
{
  public interface ITenantContext
  {
    string Key { get; set; }
  }
}

And we implement this interface based on the method we choose to identify tenants, in this case based on the subdomain:

using System.Web;
using SharpArchitecture.MultiTenant.Framework.Services;

namespace SharpArchitecture.MultiTenant.Web.Services
{
  public class TenantContext : ITenantContext
  {
    private const string DefaultStorageKey = "tenant-context-key";

    public string Key
    {
      get
      {
        if (string.IsNullOrEmpty(StoredKey)) {
          StoredKey = KeyFromRequest;
        }
        return StoredKey;
      }

      set { StoredKey = value; }
    }

    public string KeyFromRequest
    {
      get
      {
        var host = HttpContext.Current.Request.Headers["HOST"];
        var domains = host.Split('.');
        return domains.Length >= 3 ? domains[0] : string.Empty;
      }
    }

    protected string StoredKey
    {
      get { return HttpContext.Current.Items[DefaultStorageKey] as string; }
      set { HttpContext.Current.Items[DefaultStorageKey] = value; }
    }
  }
}

If a different method of identifying tenants is required, say by a query string parameter, then it is just a case of providing a different implementation of ITenantContext.

We can now create a multi-tenant repository that uses ITenantContext to select the NHibernate session based on the key:

using NHibernate;
using SharpArch.Data.NHibernate;
using SharpArchitecture.MultiTenant.Framework.Services;

namespace SharpArchitecture.MultiTenant.Data.Repositories
{
  public class MultiTenantRepository<T> : Repository<T>
  {
    private readonly ITenantContext _tenantContext;

    public MultiTenantRepository(ITenantContext tenantContext)
    {
      _tenantContext = tenantContext;
    }

    protected override ISession Session
    {
      get
      {
        var key = _tenantContext.Key;
        return string.IsNullOrEmpty(key) ? base.Session : NHibernateSession.CurrentFor(key);
      }
    }
  }
}

Next we need to create a repository interface:

using MvcContrib.Pagination;
using SharpArch.Core.PersistenceSupport;

namespace SharpArchitecture.MultiTenant.Core.RepositoryInterfaces
{
  public interface ICustomerRepository : IRepository<Customer>
  {
    IPagination<Customer> GetPagedList(int pageIndex, int pageSize);
  }
}

and implementation for our multi-tenant entities:

using MvcContrib.Pagination;
using NHibernate.Criterion;
using SharpArchitecture.MultiTenant.Core;
using SharpArchitecture.MultiTenant.Core.RepositoryInterfaces;
using SharpArchitecture.MultiTenant.Framework.Services;

namespace SharpArchitecture.MultiTenant.Data.Repositories
{
  public class CustomerRepository : MultiTenantRepository<Customer>, ICustomerRepository
  {
    public CustomerRepository(ITenantContext tenantContext) : base(tenantContext)
    {
    }

    public IPagination<Customer> GetPagedList(int pageIndex, int pageSize)
    {
      var firstResult = (pageIndex - 1) * pageSize;
      var customers = Session.QueryOver<Customer>()
        .OrderBy(customer => customer.Code).Asc
        .Skip(firstResult)
        .Take(pageSize)
        .Future<Customer>();

      var totalCount = Session.QueryOver<Customer>()
        .Select(Projections.Count<Customer>(customer => customer.Code))
        .FutureValue<int>();

      return new CustomPagination<Customer>(customers, pageIndex, pageSize, totalCount.Value);
    }
  }
}

Our controllers can now make use of ICustomerRepository without having to worry about any of the multi-tenancy issues.

We also need to update the TransactionAttribute so that it makes use of the appropriate NHibernate session:

using Microsoft.Practices.ServiceLocation;
using SharpArchitecture.MultiTenant.Framework.Services;

namespace SharpArchitecture.MultiTenant.Framework.NHibernate
{
  public class TransactionAttribute : SharpArch.Web.NHibernate.TransactionAttribute
  {
    public TransactionAttribute()
      : base(FactoryKey)
    {
    }

    protected static string FactoryKey
    {
      get
      {
        var tenantContext = ServiceLocator.Current.GetInstance<ITenantContext>();
        return tenantContext.Key;
      }
    }
  }
}

Next up, we need to update the initialisation in Global.asax.cs so that we create a session factory for the master database and also for each tenant:

        private void InitializeNHibernateSession()
        {
          var mappingAssemblies = new [] { Server.MapPath("~/bin/SharpArchitecture.MultiTenant.Data.dll") };

          var configFile = Server.MapPath("~/NHibernate.config");
          NHibernateSession.Init(
                webSessionStorage,
                mappingAssemblies,
                new AutoPersistenceModelGenerator().Generate(),
                configFile);

            var tenantConfigFile = Server.MapPath("~/NHibernate.tenant.config");
            var multiTenantInitializer = ServiceLocator.Current.GetInstance<IMultiTenantInitializer>();
            multiTenantInitializer.Initialize(mappingAssemblies, new MultiTenantAutoPersistenceModelGenerator(),  tenantConfigFile);
        }

In the code above, the standard NHibernate.config file is used to configure the master database. Whilst NHibernate.tenant.config, along with the connection string provided by the Tenant, is used to configure the tenant databases:

using SharpArch.Data.NHibernate.FluentNHibernate;

namespace SharpArchitecture.MultiTenant.Framework.Services
{
  public interface IMultiTenantInitializer
  {
    void Initialize(string[] mappingAssemblies, IAutoPersistenceModelGenerator modelGenerator, string tenantConfigFile);
  }
}
using System.Collections.Generic;
using NHibernate.Cfg;
using SharpArch.Core.PersistenceSupport;
using SharpArch.Data.NHibernate;
using SharpArch.Data.NHibernate.FluentNHibernate;
using SharpArchitecture.MultiTenant.Core;
using SharpArchitecture.MultiTenant.Framework.Services;

namespace SharpArchitecture.MultiTenant.Framework.NHibernate
{
  public class MultiTenantInitializer : IMultiTenantInitializer
  {
    private readonly IRepository<Tenant> _tenantRepository;

    public MultiTenantInitializer(IRepository<Tenant> tenantRepository)
    {
      _tenantRepository = tenantRepository;
    }

    public void Initialize(string[] mappingAssemblies, IAutoPersistenceModelGenerator modelGenerator, string tenantConfigFile)
    {
      var tenants = _tenantRepository.GetAll();
      foreach (var tenant in tenants) {
        Initialize(mappingAssemblies, modelGenerator, tenantConfigFile, tenant);
      }
    }

    private static void Initialize(string[] mappingAssemblies, IAutoPersistenceModelGenerator modelGenerator, string tenantConfigFile, Tenant tenant)
    {
      var properties = new Dictionary<string, string>
                         {
                           { "connection.connection_string", tenant.ConnectionString }
                         };
      AddTenantConfiguration(tenant.Domain, mappingAssemblies, modelGenerator, tenantConfigFile, properties);
    }

    private static Configuration AddTenantConfiguration(string factoryKey, string[] mappingAssemblies, IAutoPersistenceModelGenerator modelGenerator, string cfgFile, IDictionary<string, string> cfgProperties)
    {
      return NHibernateSession.AddConfiguration(factoryKey,
        mappingAssemblies,
        modelGenerator.Generate(),
        cfgFile,
        cfgProperties,
        null, null);
    }
  }
}

This code iterates through the list of tenants from the master database, setting the connection string and session factory key from the tenant properties and adds the configuration to NHibernate.

All that is left is to generate the mappings. In AutoPersistenceModelGenerator we move the creation of the standard mapping configuration into a method so that it can be overridden:

    protected virtual IAutomappingConfiguration GetAutomappingConfiguration()
    {
      return new AutomappingConfiguration();
    }

And derive from AutoPersistenceModelGenerator to create our multi-tenant mapping configuration:

using FluentNHibernate.Automapping;

namespace SharpArchitecture.MultiTenant.Data.NHibernateMaps
{
  public class MultiTenantAutoPersistenceModelGenerator : AutoPersistenceModelGenerator
  {
    protected override IAutomappingConfiguration GetAutomappingConfiguration()
    {
      return new MultiTenantAutomappingConfiguration();
    }
  }
}

Then we add a method to AutomappingConfiguration to determine if a type is a multi-tenant entity (by checking to see if the type implements our IMultiTenantEntity interface):

    public bool IsMultiTenantEntity(Type type)
    {
      return type.GetInterfaces().Any(x => x == typeof(IMultiTenantEntity));
    }

and update the ShouldMap method to only map entities that are not multi-tenant:

    public override bool ShouldMap(Type type)
    {
      var isMultiTenantEntity = IsMultiTenantEntity(type);
      var shouldMap = type.GetInterfaces().Any(x =>
                                      x.IsGenericType && x.GetGenericTypeDefinition() == typeof (IEntityWithTypedId<>) &&
                                      !isMultiTenantEntity);
      return shouldMap;
    }

It is then a case of deriving from AutomappingConfiguration to map the multi-tenant entities:

using System;

namespace SharpArchitecture.MultiTenant.Data.NHibernateMaps
{
  /// <summary>
  ///
  /// </summary>
  public class MultiTenantAutomappingConfiguration : AutomappingConfiguration
  {
    public override bool ShouldMap(Type type)
    {
      var shouldMap = IsMultiTenantEntity(type);
      return shouldMap;
    }
  }
}

A sample application is available on GitHub.