使用Symfony2和Doctrine2开发多租户应用程序的最佳实践

时间:2011-09-24 12:34:01

标签: php doctrine-orm symfony saas multi-tenant

我正在开发一个需要支持多租户模型的应用程序。我正在使用symfony2 php框架和doctrine2。

我不确定构建此要求的最佳方法。 Symfony的ACL功能是否提供了解决方案的一部分?

您可以提供哪些建议或想法?是否有任何已实施此策略的示例symfony2应用程序或开源应用程序?

我的第一个想法是在所有表中使用tenant_id列,并将其与应用程序中的帐户对象相关联。我不确定ACL是否应该处理我想要做的事情,或者你是否还要对所有针对你的对象的查询负责,这样他们就不会返回未经授权的数据。

如果我没有使用Doctrine,可能很容易说只是在每个查询中添加Where tenant_id = @accountid,但我不确定这是否是正确的方法。

谢谢

6 个答案:

答案 0 :(得分:5)

我们已经做了一段时间了(虽然没有symfony和doctrine,但问题仍然存在) - 我们从一个庞大的数据库开始,并在每个表中的每行实现一个“环境标识符”。这种模式迁移很简单:所有代码都是统一的,因此模式更改只是对代码和模式的单一更改。

然而,这会导致速度问题(大表),敏捷性(移动/备份等大型数据集比很多小型数据集更加密集),当然更容易破碎的环境,因为单个故障会导致每个数据集都被拉下来系统......

然后我们切换到多个数据库;每个环境都有自己的架构。通过利用Doctrine提供的迁移(在我们的例子中为1),我们能够快速更新整个应用程序或仅单个环境。此外,在帐篷之间移动特定变化的能力允许更快的速度和优化精度。

我的建议是:创建一个扩展到不同租户的单个核心,并保留每个帐户的本地自定义数据库配置。 (在类似app.ini的结构中)

/
    apps/
        tentant1/
            models/ <--- specific to the tenant
            libraries/ <--- specific to the tenant
            config/
                app.ini <--- specific configuration
        tentant2/
        /**/ etc
    core/
        models/ <--- system wide models
        libraries/ <--- system wide libraries (i.e. doctrine)
        core.ini <--- system wide configuration

这可以保持一切井然有序。我们甚至可以为每个帐篷提供核心的结构。因此能够覆盖特定于租户的“核心”的每个方面。

答案 1 :(得分:3)

我们使用我们的主要解决方案之一来实现这一点,这当然是可能的。我们使用Symfony2的bundle来创建一个“基础”包,然后由其他每个客户端包进行扩展。

那就是说,我们正在考虑将来以这种方式做事。多租户的决定对我们来说并不合适,因为我们的许多客户对他们的数据与其他人的数据在同一个数据库表中感到不舒服。这完全不包括表格增长时性能缓慢的潜在问题。

我们还发现,除非它得到很好的控制,否则Doctrine 2会有一些相当严重的问题。虽然这可能是结构不良的代码和数据库逻辑的副作用,但我觉得ORM能够达到导致致命错误的点是一个漏洞,因为它使用了太多的内存 - 特别是当只是因为它使用了如此多的内存是因为它正在对SQL查询进行批处理,以便它们可以“更高效”。

这纯粹是我的观点,当然:)对我们来说不起作用可能对你有用,但我觉得你最好每个客户保留单独的数据库,即使它们都存储了在同一台服务器上。

答案 2 :(得分:1)

BEST以不同的方式构建不同的通知。请更具体地提问。开发多租户系统的方法之一是在所有表中放置一个公共主键来建立关系。主键的类型和性质是可靠的项目。

答案 3 :(得分:1)

为什么不为每个客户端尝试不同的数据库,以保持数据分离,并为您的应用提供唯一的入口点。例如:http://client1.project.net与路由系统将映射到client1数据库。坏的一面是:更复杂的数据库更改,因为每个客户端的所有数据库都需要升级。

答案 4 :(得分:1)

我认为,使用symfony 2/3管理多租户多数据库。 我们可以为ORM的学说配置auto_mapping: false。 file:config.yml

doctrine:
    dbal:
        default_connection: master
        connections:
            master:
                driver:   pdo_mysql
                host:     '%master_database_host%'
                port:     '%master_database_port%'
                dbname:   '%master_database_name%'
                user:     '%master_database_user%'
                password: '%master_database_password%'
                charset:  UTF8
            tenant:
                driver:   pdo_mysql
                host:     '%tenant_database_host%'
                port:     '%tenant_database_port%'
                dbname:   '%tenant_database_name%'
                user:     '%tenant_database_user%'
                password: '%tenant_database_password%'
                charset:  UTF8

    orm:
        default_entity_manager: master
        auto_generate_proxy_classes: "%kernel.debug%"
        entity_managers:
            master:
                connection: master
                auto_mapping: false
                mappings:
                    AppBundle:
                        type: yml
                        dir: Resources/master/config/doctrine
            tenant:
                connection: tenant
                auto_mapping: false
                mappings:
                    AppBundle:
                        type: yml
                        dir: Resources/tenant/config/doctrine

之后,我们无法通过request_listener中的覆盖连接信息来处理每个租户的连接,例如文章:http://mohdhallal.github.io/blog/2014/09/12/handling-multiple-entity-managers-in-doctrine-the-smart-way/ 我希望,这种做法可以帮助那些与多租户合作的人

此致

Vuong Nguyen

答案 5 :(得分:-1)

这也是我一直试图解决的问题。我能想出的最好(不是在实现中,但在理论上)是这样的:给每个租户自己的数据库登录并使用视图来阻止他们看到其他人的数据。

I ran across this link that describes a way of doing this for just plain old MySQL (not with Symfony/Doctrine).

基本上,您拥有实际的数据库表,但每个表都有一个列,用于存储构成该行的数据库用户的名称。然后创建始终按此列过滤的视图,以便每当用户登录数据库时(通过管理工具或甚至通过Symfony / Doctrine连接),它们只会返回与它们直接关联的记录。这允许您将数据“分开”,但仍然在一个数据库中。在提取数据时(例如Symfony中的实体),您将从过滤后的视图中提取数据而不是实际的数据库表。

现在,这个解决方案并不完全符合Symfony / Doctrine。我之前能够对此次运行进行非常快速和初步的测试; Doctrine能够很好地使用数据库视图(它可以插入,编辑和删除视图中的条目没问题)。但是,在创建/更新架构时,这并不好玩。当然,Symfony / Doctrine似乎具有相当的可扩展性,所以我相信有一种方法可以让它自动化,但这种设置不支持开箱即用。需要告诉Doctrine更新表,总是将用于保存用户名的列附加到它创建的实体表中,同时更新视图等等。(您还需要一种方法来加载正确的数据库配置) Symfony应用程序,主要是作为服务器和其他东西的不同登录将是相同的。)但是,如果这可以克服,你的应用程序本身可以完全“无知”其他人的数据坐在数据库中。