多租户访问控制:存储库还是服务层?

12
在基于Rob Conery的MVC Storefront的多租户ASP.NET MVC应用程序中,我应该在存储库(repository)层还是服务(service)层中过滤租户数据?

1. 在存储库(repository)中过滤租户数据:

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

2. 让服务按租户过滤存储库数据:

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

我的直觉告诉我在服务层中实现(选项2),但可以争论每个租户本质上应该拥有自己的“虚拟存储库”(选项1),其中这个责任落在存储库中。

  • 哪种方法最优雅:选项1、选项2,还是有更好的方法?

更新:

我尝试了在存储库中过滤的想法,但问题在于我的应用程序通过子域提供租户上下文,并且仅与服务层交互。将上下文一直传递到存储库层面是一项艰巨的任务。

因此,我选择在服务层中过滤我的数据。我认为存储库应该表示存储库中所有可用的物理数据,并带有检索特定租户数据的适当过滤器,以供服务层使用。

最终更新:

由于不必要的复杂性,我最终放弃了这种方法。请参见下面的我的答案。


+1 这可以帮助我避免询问几乎相同的问题。 - Ahmad
2个回答

5
@FreshCode,我们在仓库中处理,并且不将租户作为参数传递。我们使用以下方法:
public IQueryable<Job> GetJobs()
{
    return _db.Jobs.Where(j=>j.TenantId == Context.TenantId);
}

上下文是存储库具有的依赖项,并在BeginRequest中创建,其中您可以根据URL(例如)确定租户。我认为这样很透明,您可以避免使用可能会有点干扰的tenantId参数。
问候。

+1 感谢您的快速回答。我猜 Context 只包含 TenantId。您能否给出您的 TenantContext 实现的示例?您是在存储库构造函数中实例化它吗? - Petrus Theron
是的,它是一个只有TenantId的接口,但它可能包含其他内容,比如UserId等。我们使用StructureMap,并通过存储库的构造函数注入上下文的实现。 - uvita
那么,我应该在每个 Repository 类中添加 private ITenantContext _tenantContext; 并在每个构造函数中添加一个参数吗?还是有一种更优雅的方法可以从基接口继承上下文以确保它始终被注入而不会使构造函数混乱? - Petrus Theron
嗨@uvita - 我希望你有时间看一下我发布的一个类似的问题 - 我喜欢你使用上下文来解决这个问题的方法,并且很好奇在3年后你的方法是否有所改变:http://stackoverflow.com/questions/19756369/making-an-object-accessible-by-service-layer-without-passing-as-parameter-in-mvc - SB2055
@SB2055,我猜是同样的问题,请看我的回答。 - uvita
显示剩余9条评论

3
更新:不采用多租户方法给我带来了数百小时的技术债务。四年后,我希望我花时间先实现一个干净的租户方法。不要犯同样的错误!

旧的、过时的答案:

最终我放弃了所有多租户代码,改为为每个租户使用单独的应用程序和数据库。在我的情况下,我有一些很少更改的租户,所以我可以这样做。

我的所有控制器、成员提供程序、角色提供程序、服务和存储库都在各处重复使用 .WithTenantID(...) 代码,这让我意识到我并不真正需要一个 Users 表来访问大部分时间都特定于一个租户的数据,所以使用单独的应用程序更有意义,而且使一切变得更简单。

感谢你们的回答 - 它们让我意识到我需要重新设计。


1
然而,每次想要添加功能、修复错误等时,都需要更新应用程序和数据库的每个实例。对于其他人来说,这可能是@FreshCode情况下正确的方法,但在跟随相同路径之前,请考虑缺点。(我很想听听自从发布此问题以来,FreshCode的应用程序如何发展) - Chris
3
是的,这个应用程序后来发展壮大了,而我当时的这个决定给我带来了麻烦。不可避免地,许多用户和大量数据必须在“租户”之间共享,现在有很多库存问题。然而,那时这个决定可能是最实际的。 - Petrus Theron
感谢 @FreshCode 提供如此诚实的更新 - 我相信这将帮助引导其他人。 - Chris
你可不可以为每个租户使用单独的数据库?在运行时,根据已登录的用户实例化带有基于租户的连接字符串的DbContext,并将其注入到你的仓库中... - Yashvit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接