实施领域驱动设计:为什么要在所有存储库查询中包含 TenantId?

Implementing Domain Driven Design: Why include TenantId in all repository queries?

我试图理解为什么 Vaughn Vernon(在红皮书的 Github 示例代码中)在每个存储库 get 或 find 方法中包含 tenantId。特别是那些正在做基本 getById 的人。

在一个示例中,他使用用户名来提供用户身份,以及用户名在租户内必须唯一的业务规则,因此:

class UserRepository:
    public User userWithUsername(TenantId aTenantId, String aUsername)

很有道理。

但在他的其他 BC 中,他使用的是基于 GUID 的标识值对象,但在从存储库检索时仍将其与 tenantId 结合:

class ProductRepository:
    public Product productOfId(TenantId aTenantId, ProductId aProductId)

即使是 CQRS 示例,也将 tenantId 和 entityId 的组合用于其存储库单一 get 方法。

它无处不在,但我不明白为什么有必要。如果产品、积压项目、论坛、日历等都有全球唯一标识符,为什么还需要更多的东西来查询它们?

如果使用它来确保只能检索特定租户的实体 - 这似乎不是存储库的预期责任,而是身份验证等,那么为什么要包含在每个存储库查询方法中?

请注意,我理解为什么需要将 tenantId 作为实体的属性,以及如何使用它来强制执行某些唯一约束。但是,在检索产品时,productOfId(ProductId aProductId) 不够吗?

他确实谈到了这一点,“在多租户环境中,TenantId 实例也被视为唯一身份的一部分。”结合两个 GUID 来确定身份与仅一个 GUID 相比有什么价值?

我认为这不是 DDD 特有的,而是多租户特有的。无论您如何设计应用程序,您都需要能够分辨记录或实体是属于租户还是其他租户。因此,即使 ID 是全局唯一的,因此不需要另一个 ID 部分来进行重复数据删除,您仍然需要租户 ID。

从更实际的角度来看,如果您需要在该数据之上提供 restful API,则可以在每个实体上找到租户 ID 的实用程序。您的 API 结构很可能如下所示:

api/{tenantId}/entity-type/{entityId}

您必须验证执行请求的用户是否可以访问给定的 'tenantId',例如,基于身份验证令牌中的声明。如果用户有访问权限,那么您将从数据库中读取。但是,如果您的存储库只接受 'entityId',那么它将 return 该实体,而不管它属于哪个租户,来自 tenant1 的用户可以从任何其他只知道 ID 的租户那里获取数据。当然,您可以在加载实体后添加对租户 ID 的检查,但迟早您会忘记添加它。相反,如果您遵循始终将 'tenantId' 添加到存储库的做法,那么此检查将内置到查询本身中,从而使整个过程更加一致和高效。

附带说明一下,还有其他方法可以对所有查询添加租户 ID 检查,这将实现相同的目标,而无需手动将其传递给每个存储库方法调用和查询实现。例如,您可以将 tenantId 放在上下文中并使用 DI 解析您的存储库来注入它。使用 SQL 服务器,您可以使用行级安全性,以便 tenantId 检查成为 table 策略的一部分,而不是将其添加到 table 上的所有查询中。