如何将现有的 rails 应用程序迁移到带有 Apartment 的多租户应用程序?

How to migrate existing rails application to multi-tenant application with Apartment?

我正在努力将现有的单租户应用程序迁移到 ApartmentDevise 的多租户应用程序而不使用子域。

我已经有一个应用程序 Company 和属于该公司的多个用户。
现在我需要扩展该应用程序并添加另一个 Company。每个公司的用户都是唯一的,并通过电子邮件进行识别。

所以我想要的是 - 当用户尝试登录时 - 我想根据用户电子邮件切换到正确的租户并继续流程。

我想将所有 User 数据保存在单独的数据库中。在这里我不明白如何查找正确的租户?所有的指南和例子都展示了当 User 添加到 excluded_models 时如何处理。但这对我不起作用。

看来,应该有一些 public 模式,其中 UserEmail_Tenant table 是什么?还是我遗漏了什么?

你真的只有两个选择:

  1. 确保用户通过特定租户登录 URL
  2. 有办法将登录凭据映射到租户

(2) 将登录凭据映射到租户的问题在于它禁止凭据重复使用,当用户想要同时访问您的 2 个租户时,这是一个阻碍。要处理这种情况,您必须为每个用户提供一个全球唯一的租户特定 ID。你可以做到

  • 通过在用户 ID 中包含某种租户标识符(丑陋)或
  • 强制拥有 1 个以上租户账户的用户使用每个租户不同的全球唯一 ID 登录(糟糕的用户体验,您的客户支持团队很难解释)。

大多数公司认为这些选择是不可接受的,但我偶尔看到有人这样做。

大多数公司更愿意允许用户在拥有多个租户帐户的情况下将他们的电子邮件地址重新用作他们的 ID。这些公司选择通过给每个租户一个唯一的登录名 URL 来隔离租户帐户,并且不允许通过他们的(具有多租户应用程序的服务提供商公司)主站点直接登录。相反,他们要求用户转到租户的站点并单击那里的某种 login 按钮,这会将他们定向到特定于租户的登录页面。

特定于租户的选项 URL 包括:

  • 要求登录名 URL 在 URL 中有一个 tenant_id 参数。例如https://example.com/login?tenant_id=tenant.com
  • 要求登录 URL 在租户特定的子域上。

大多数公司选择子域有两个原因:

  1. 它比在 URL.
  2. 中放置租户 ID 参数更简洁 URL
  3. 这意味着每个租户都可以有完全相同的路径并且每个租户都没有租户参数URL,这可以使开发和测试更容易,SEO 更有效。
  4. 在一定程度上页面内容因租户而异,它更好地支持搜索引擎和 SEO,因为页面变体位于不同的域中,不会折叠到一个域中并由一些不透明的参数触发。
  5. 它使分片更容易。当您在一台服务器上达到合理数量的租户时,您可以通过利用子域将流量路由到下一组租户的不同服务器(集群)来轻松地水平扩展,而不是跳来跳去继续垂直扩展。

因此,虽然您可以创建租户的电子邮件登录映射,但我不建议这样做,因为一旦用户尝试使用同一电子邮件向第二个租户创建帐户,它就会痛苦地失败。即使您认为没有人会这样做,您也不能确定,尤其是随着公司的发展。即使你的 99.99% 的用户不这样做,当你达到 100 万用户时,你也会有 100 个这样做。

如果您承诺允许用户使用他们的电子邮件地址作为他们的用户 ID 并让他们通过公共 URL 登录,我建议让您的用户也指明他们正在登录哪个租户,通过文本框或选项菜单,您可以根据 cookie 值 pre-fill/select。在这种情况下,要登录,您必须提供用户 ID、租户 ID 和密码。

如果您同意用户不使用他们的电子邮件地址作为用户 ID,那么我只会将租户 ID 作为用户 ID 的一部分。因此,租户 'apt' 中的用户 'steve' 可能具有用户 ID 'steve_apt'。

这 2 个选项既允许用户在多个租户上拥有帐户,也可以在没有任何用户 ID 到租户的全局映射的情况下工作。

如果您承诺允许用户使用他们的电子邮件地址作为他们的用户 ID 并让他们通过通用 URL 登录,并且您不希望他们必须意识到以下事实公司是您系统上许多租户中的一个,那么是的,您需要某种用户 ID 到租户映射。

您可以通过多种方式提供此映射。

  • 您可以在浏览器中放置一个 cookie,以提示您用户属于哪个租户,然后只需查询该租户即可。如果查询失败或未提供 cookie,您可以回退到轮询所有租户。
  • 您可以通过一些后台作业或按需将用户 ID 和租户之间的映射填充到 Redis 缓存中,并在添加新用户时推送更新,这样您就可以在 Redis 缓存中快速查找几乎所有内容时间,再次回退到仅在缓存未命中的罕见情况下轮询每个租户。
  • 您可以像对待第三方提供商一样对待登录。创建一个只存储用户 ID、密码和租户的应用程序,让人们登录该应用程序,然后该应用程序将登录的用户转发到主应用程序,传递经过身份验证的用户 ID 和租户 ID。如果所有应用程序都在同一个域中,那么这应该不难,因为所有身份验证应用程序需要做的就是创建其他应用程序已经接受的相同 cookie 作为对登录用户的身份验证。

轮询租户以查找用户 ID 也可以通过几种不同的方式完成。我一般不推荐这种方法,但如果你打算这样做,我建议你使用 PostgreSQL 数据库,将每个租户保存在单独的 schema 在同一个 数据库 中并使用 物化视图 或 PL/pgSQL 存储动态 SQL 过程。

我不是这些解决方案中的任何一个的忠实粉丝,但我想如果我必须选择我会选择包含用户 ID、密码和 tenant_id 的 authentication 模式并且仅用于登录,最好由隔离且更安全的应用程序使用。你如何处理这需要用户 ID 到租户映射存在于两个不同的地方,这两个地方都可以声称是事实的来源,以及当他们不同意时你做什么,这是一个问题,我的主要建议是一开始不要那样做。