12

In a multi-tenant ASP.NET MVC application based on Rob Conery's MVC Storefront, should I be filtering the tenant's data in the repository or the service layer?

1. Filter tenant's data in the repository:

public interface IJobRepository
{
    IQueryable<Job> GetJobs(short tenantId);
}

2. Let the service filter the repository data by tenant:

public interface IJobService
{
    IList<Job> GetJobs(short tenantId);
}

My gut-feeling says to do it in the service layer (option 2), but it could be argued that each tenant should in essence have their own "virtual repository," (option 1) where this responsibility lies with the repository.

  • Which is the most elegant approach: option 1, option 2 or is there a better way?

Update:

I tried the proposed idea of filtering at the repository, but the problem is that my application provides the tenant context (via sub-domain) and only interacts with the service layer. Passing the context all the way to the repository layer is a mission.

So instead I have opted to filter my data at the service layer. I feel that the repository should represent all data physically available in the repository with appropriate filters for retrieving tenant-specific data, to be used by the service layer.

Final Update:

I ended up abandoning this approach due to the unnecessary complexities. See my answer below.

Petrus Theron
  • 27,855
  • 36
  • 153
  • 287

2 Answers2

5

@FreshCode, we do it in the repository, and we do not pass the tenant as a parameter. We use the following approach:

public IQueryable<Job> GetJobs()
{
    return _db.Jobs.Where(j=>j.TenantId == Context.TenantId);
}

The context is a dependency the repository has and that is created in the BeginRequest where you determine the tenant based on the url for example. I think in this way it's pretty transparent and you can avoid the tenantId parameter which may become a little bit disturbing.

Regards.

uvita
  • 4,124
  • 1
  • 27
  • 23
  • +1 Thanks for the quick answer. I'm guessing Context just contains a TenantId. Could you give an example of your `TenantContext` implementation? Do you instantiate it in your repository contstructor? – Petrus Theron Apr 30 '10 at 15:34
  • Yes, it is an interface that has only a TenantId, but it may hold other things, like UserId, etc. We use structuremap and inject an implementation of the context via the constructor of the repository. – uvita Apr 30 '10 at 15:39
  • Awesome, I'm new to DI but am also using StructureMap for this. – Petrus Theron Apr 30 '10 at 18:59
  • are you instantiating your `TenantContext` as a parameter of the repository's constructor or with `var tenantContext = ObjectFactory.GetInstance();` inside the constructor? Trying to figure out the best-practice. – Petrus Theron Apr 30 '10 at 19:26
  • We´re using constructor-injection for this as the TenantContext in this case is a class dependency. Let me recommend you a reading on this topic by the way http://jeffreypalermo.com/blog/constructor-over-injection-anti-pattern/ It´s worth reading it. Regards. – uvita Apr 30 '10 at 20:27
  • So, I should add `private ITenantContext _tenantContext;` to each Repository class and add a parameter in every constructor? Or is there a more elegant way to inherit the context from a base interface to ensure it is always injected without cluttering the constructors? – Petrus Theron May 01 '10 at 10:28
  • @uvita: How do you deal with joins for child objects of a given Job in an SQL repository? For example: I'm having to add two joins in `GetPayments()`, where each `Payment` belongs to an `Invoice`, which belongs to a `Job`, which in turn belongs to a `Branch` (Tenant) to check the context ID. Seems like quite a performance hit. Perhaps use Views? – Petrus Theron May 01 '10 at 11:59
  • @FreshCode, that it´s up to you. You should know which are the weak and which are the strong entities. Those strong entities should have a Tenant related and the weak entities should be accessed via the strong entities. Regarding the previous comment, I have a base repository that has the TenantContext property (among others), and all other repositories extend that base class. – uvita May 01 '10 at 14:49
  • uvita, thanks for your comments. I've been struggling all day to implement multi-tenancy with StructureMap: my `TenantContext` can be inferred from a `{tenant}` URL routing parameter, but I can't seem to figure out how to make StructureMap inject a repository instance which will only return data for the current tenant. You mentioned you use constructor-injection for this. Any chance of a code sample? – Petrus Theron May 01 '10 at 15:18
  • Just saw your answer to my other question. I'm looking into the `Inject` method now. – Petrus Theron May 01 '10 at 15:23
  • @uvita, if tenant data is filtered at the repository layer, then there is no way to retrieve more than one tenant's data at once (barring generic GetData() methods). Wouldn't this make the service layer more appropriate for tenant data filtering? – Petrus Theron May 01 '10 at 15:39
  • I like the idea of a tenant context. I also agree that filtering data at the repo level provides a level of comfort that your service will operate on tenant specific data only. Re your last comment - the service does not need to be aware of which tenant's data its operating on, yes maybe further filtering can be done at this layer – Ahmad May 05 '10 at 06:16
  • Hi @uvita - I was hoping you might have time to take a look at a similar question I've posted - I like your use of Context to solve this problem and am curious if your approach has changed after 3 years: http://stackoverflow.com/questions/19756369/making-an-object-accessible-by-service-layer-without-passing-as-parameter-in-mvc – SB2055 Nov 03 '13 at 18:36
  • @SB2055, I guess it´s the same thing, please see my answer to your question. – uvita Nov 06 '13 at 12:28
3

Update: Not going with a multi-tenant approach cost me hundreds of hours in technical debt. Four years down the line, I wish I took the time to implement a clean tenant approach first. Don't make the same mistake!


Old, out-dated answer:

I ended up stripping out all multi-tenant code in favour of using separate applications and databases for each tenant. In my case I have few tenants that do not change often, so I can do this.

All my controllers, membership providers, role providers, services and repositories were gravitating toward duplicate .WithTenantID(...) code all over the place, which made me realize that I didn't really need one Users table to access data that is specific to one tenant 99% of the time, so using separate applications just makes more sense and makes everything so much simpler.

Thanks for your answers - they made me realize that I needed a redesign.

Petrus Theron
  • 27,855
  • 36
  • 153
  • 287
  • 1
    ... but does require that every instance of your application and database needs to be updated each time you want to add a feature, fix a bug, etc. For others reading this, it may be the right approach for @FreshCode's situation, but do consider the downsides before following the same path. (I'd be interested to hear how FreshCode's application evolved over the last couple of years since this question was posted) – Chris Aug 07 '12 at 15:38
  • 3
    Yeah, this application has since grown and this decision bit me in the ass later. Inevitably many users and lots of data had to be shared between "tenants" and now there are a lot of inventory issues. However, the decision was probably the most practical at the time. – Petrus Theron Aug 07 '12 at 15:57
  • Thanks for the very honest update @FreshCode - will help to steer others I'm sure. – Chris Aug 08 '12 at 07:31
  • Could you not just use separate Database for each tenant? at runtime based on the logged in user instantiate your DbContext with the tenant based connection string and inject it into your repository... – Yashvit Aug 28 '13 at 13:14