Multi-tenancy on S#arp Architecture Revisited

February 9, 2011

Following my previous post some issues were pointed out with the implementation, the main one being that the correct repository implementation was not resolved from an IRepository<T> interface (see the Google Group discussion for more details).

As mentioned in the discussion I have made some minor modifications to S#arp Architecture to solve this problem and better support multi-tenancy. You can see the changes in the pull request, but it looks like they are now included in the latest release.

The changes are very minor, and centre around the introduction of an interface, ISessionFactoryKeyProvider, so that it is possible to get the session factory key without having to use an attribute:

namespace SharpArch.Data.NHibernate
{
  public interface ISessionFactoryKeyProvider
  {
    /// &lt;summary&gt;
    /// Gets the session factory key.
    /// &lt;/summary&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    string GetKey();

    /// &lt;summary&gt;
    /// Gets the session factory key.
    /// &lt;/summary&gt;
    /// &lt;param name="anObject"&gt;An optional object that may have an attribute used to determine the session factory key.&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    string GetKeyFrom(object anObject);
  }
}

I’ve created a default implementation of this interface that just delegates getting the key from the existing SessionFactoryAttribute.

namespace SharpArch.Data.NHibernate
{
  /// &lt;summary&gt;
  /// Implementation of &lt;see cref="ISessionFactoryKeyProvider" /&gt; that uses
  /// the &lt;see cref="SessionFactoryAttribute" /&gt; to determine the session
  /// factory key.
  /// &lt;/summary&gt;
  public class DefaultSessionFactoryKeyProvider : ISessionFactoryKeyProvider
  {
    public string GetKey()
    {
      return NHibernateSession.DefaultFactoryKey;
    }

    /// &lt;summary&gt;
    /// Gets the session factory key.
    /// &lt;/summary&gt;
    /// &lt;param name="anObject"&gt;An object that may have the &lt;see cref="SessionFactoryAttribute"/&gt; applied.&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public string GetKeyFrom(object anObject)
    {
      return SessionFactoryAttribute.GetKeyFrom(anObject);
    }
  }
}

I’ve also added a helper class SessionFactoryKeyHelper:

using SharpArch.Core;

namespace SharpArch.Data.NHibernate
{
  public static class SessionFactoryKeyHelper
  {
    public static string GetKey()
    {
      var provider = SafeServiceLocator&lt;ISessionFactoryKeyProvider&gt;.GetService();
      return provider.GetKey();
    }

    public static string GetKey(object anObject)
    {
      var provider = SafeServiceLocator&lt;ISessionFactoryKeyProvider&gt;.GetService();
      return provider.GetKeyFrom(anObject);
    }
  }
}

Now whenever S#arp Architecture requires a session factory key we can use the helper class, rather than using SessionFactoryAttribute e.g. in the Repository implementation the code is changed from:

protected virtual ISession Session {
  get {
    string factoryKey = SessionFactoryAttribute.GetKeyFrom(this);
    return NHibernateSession.CurrentFor(factoryKey);
  }
}

to:

protected virtual ISession Session {
  get {
    string factoryKey = SessionFactoryKeyHelper.GetKey(this);
    return NHibernateSession.CurrentFor(factoryKey);
  }
}

Note: this change to the Repository implementation means that the MultiTenantRepository class from my previous post is no longer required.

Similar changes are also made to TransactionAttribute and EntityDuplicateChecker.

If you do not need multi-tenancy, or are happy to use the existing TransactionAttribute to specify the session factory key, then you just need to register the DefaultSessionFactoryKeyProvider implementation in the container:

container.AddComponent("sessionFactoryKeyProvider", 
  typeof(ISessionFactoryKeyProvider),
  typeof(DefaultSessionFactoryKeyProvider));

But if you want to provide the session factory key by any other means, it is just a case of implementing and registering your implementation of ISessionFactoryKeyProvider.

In my sample application I’ve implemented it as below:

using System;
using System.Linq;
using SharpArch.Data.NHibernate;
using SharpArchitecture.MultiTenant.Framework.Contracts;
using SharpArchitecture.MultiTenant.Framework.Extensions;
using SharpArchitecture.MultiTenant.Framework.Services;

namespace SharpArchitecture.MultiTenant.Framework.NHibernate
{
  public class MultiTenantSessionFactoryKeyProvider : ISessionFactoryKeyProvider
  {
    private readonly ITenantContext _tenantContext;

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

    public string GetKey()
    {
      var key = _tenantContext.Key;
      return string.IsNullOrEmpty(key) ? NHibernateSession.DefaultFactoryKey : key;
    }

    public string GetKeyFrom(object anObject)
    {
      var type = anObject.GetType();
      return IsMultiTenantRepository(type) || IsRepositoryForMultiTenantEntity(type)
        ? GetKey()
        : NHibernateSession.DefaultFactoryKey;
    }

    public bool IsMultiTenantRepository(Type type)
    {
      return type.IsImplementationOf&lt;IMultiTenantRepository&gt;();
    }

    public bool IsRepositoryForMultiTenantEntity(Type type)
    {
      if (!type.IsGenericType) {
        return false;
      }

      var genericTypes = type.GetGenericArguments();
      if (!genericTypes.Any()) {
        return false;
      }

      var firstGenericType = genericTypes[0];
      return firstGenericType.IsImplementationOf&lt;IMultiTenantEntity&gt;();
    }
  }
}

This makes use of a couple of marker interfaces IMultiTenantEntity and IMultiTenantRepository to decide whether we need to get the tenant, or the default, session factory key. Actually getting the tenant session factory key is accomplished by the implementation of ITenantContext.

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 &gt;= 3 ? domains[0] : string.Empty;
      }
    }

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

I’ve update my sample application on GitHub with these changes and to run against  S#arp Architecture v1.95.