views:

273

answers:

2

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.

+2  A: 

@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
+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?
FreshCode
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
Awesome, I'm new to DI but am also using StructureMap for this.
FreshCode
are you instantiating your `TenantContext` as a parameter of the repository's constructor or with `var tenantContext = ObjectFactory.GetInstance<ITenantContext>();` inside the constructor? Trying to figure out the best-practice.
FreshCode
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
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?
FreshCode
@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?
FreshCode
@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
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?
FreshCode
Just saw your answer to my other question. I'm looking into the `Inject` method now.
FreshCode
@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?
FreshCode
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
A: 

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.

FreshCode