如何在Laravel 5中使用orchestra /tenanti来构建具有多个数据库的多租户应用程序?

时间:2022-12-02 12:49:08

I am trying to build and application using Laravel 5. It is supposed to be a multi tenant database architecture using multiple databases. My employer requires this for security purposes.

我正在尝试使用Laravel 5构建和应用程序。它应该是一个使用多个数据库的多承租者数据库体系结构。我的雇主出于安全考虑需要这样做。

I have tried manually managing the main DB migrations and the Tenant migrations but failed. So I decided to take the help of a Laravel specific package which is supposedly what I require.

我尝试过手动管理主DB迁移和租户迁移,但失败了。所以我决定借助Laravel特有的软件包,这应该是我所需要的。

Tenanti provides a way to have my purpose solved but the problem is that me being a novice developer, am not able to fully understand how to use it in my application.

Tenanti提供了一种解决我的目的的方法,但问题是,作为一个新手,我不能完全理解如何在我的应用程序中使用它。

I have installed it correctly I believe doing:

我安装正确,我相信:

composer require "orchestra/tenanti=~3.0"

Adding these providers and aliases in the config app file:

在config app文件中添加这些提供者和别名:

'providers' => [

    // ...
    Orchestra\Tenanti\TenantiServiceProvider::class,
    Orchestra\Tenanti\CommandServiceProvider::class,
],

'aliases' => [

    'Tenanti' => Orchestra\Support\Facades\Tenanti::class,

],

Finally publishing the config and tweaking it according to the documentation for multiple databases:

最后发布配置并根据多个数据库的文档对其进行调整:

php artisan vendor:publish

return [
    'drivers' => [
        'user' => [
            'model'     => App\User::class,
            'migration' => 'tenant_migrations',
            'path'      => database_path('tenanti/user'),
        ],
    ],
];

At this point I am still blurry what to do next?

在这一点上,我仍然搞不清楚下一步该做什么?

My doubts are as follows:

我的疑问如下:

  1. Where will the migration files be generated and stored? I mean there are two kinds of databases in my application obviously. One set of files is for the main DB which will store all the tenant information and the other files will be for the tenant DB. So how and where will these be stored?
  2. 迁移文件将在哪里生成和存储?显然,我的应用程序中有两种数据库。一组文件用于主DB,主DB将存储所有租户信息,其他文件将用于租户DB。那么,如何以及在哪里存储这些数据呢?
  3. I see the word 'driver' a lot in the documentation but I am not sure what driver is exactly.
  4. 我在文档中经常看到“driver”这个词,但我不确定到底是什么驱动程序。
  5. How will I handle the authentication for the application? I mean whenever a tenant logs in, I will have to make sure the connection to the database changes dynamically. How will I accomplish this?
  6. 如何处理应用程序的身份验证?我的意思是,每当租户登录时,我必须确保与数据库的连接会动态变化。我该怎么做呢?
  7. I tried to go through the repository of the package itself and make sense of the code inside but in vain. I am not very good when it comes to design patters like facades, command bus, service provider and so on, which is why I am not able to understand the flow of the package or make sense of it.
  8. 我试图遍历包本身的存储库并理解其中的代码,但徒劳无功。在设计门面、命令总线、服务提供商等方面,我不是很擅长,这就是为什么我不能理解包的流程或理解它。

I tried to run some of the artisan commands which come with the package like:

我试着运行一些随同软件包的artisan命令,比如:

php artisan tenanti:install {driver}
php artisan tenanti:make {driver} {name}

But I am getting an error like so:

但我的错误是这样的:

[InvalidArgumentException] Database connection [tenants] is not available.

[InvalidArgumentException]数据库连接[租户]不可用。

Where can I find the resources to understand how to proceed with this?

我在哪里可以找到理解如何进行此操作的资源?

2 个解决方案

#1


10  

+1 to @morphatic answer, it quiet accurate on most of the stuff.

+1到@形态回答,它对大多数东西都很准确。

Migration

One set of files is for the main DB which will store all the tenant information and the other files will be for the tenant DB. So how and where will these be stored?

一组文件用于主DB,主DB将存储所有租户信息,其他文件将用于租户DB。那么,如何以及在哪里存储这些数据呢?

For your main database you should be able to use the default database/migration and utilize php artisan make:migration and php artisan migrate.

对于主数据库,您应该能够使用默认的数据库/迁移并使用php artisan make:迁移和php artisan迁移。

Tenanti however will use the migration path set under the "driver" configuration. e.g:

不过,Tenanti将使用“驱动程序”配置下的迁移路径集。例句:

'path' => database_path('tenanti/user'),

In this case the migration will be created/migrated from database/tenanti/user (you can choose other folder and it will use that folder). Once you set this up you can create new migration file for the user tenant via php artisan tenanti:make user create_blogs_table (as an example) and run migration via php artisan tenanti:migrate user (see the similarity between Laravel migration command and Tenanti?).

在这种情况下,迁移将从数据库/tenanti/user创建/迁移(您可以选择其他文件夹,它将使用该文件夹)。一旦您设置了这个,您就可以通过php artisan tenanti为用户租户创建新的迁移文件:让用户create_blogs_table(作为示例)并通过php artisan tenanti运行迁移:迁移用户(查看Laravel迁移命令和tenanti之间的相似之处)。

Driver

Driver is just the grouping of a tenant, you maybe grouping it by users, companies, or team etc. And there is possibility that you may require more than one type of group per project, otherwise most of the time you only be using single "group" or "driver".

驱动程序只是一个租户的分组,您可能会将其分组为用户、公司或团队等。您可能需要多个类型的每个项目,否则大多数情况下您只使用单个“组”或“驱动程序”。

Authentication or Accessing DB

How will I handle the authentication for the application? I mean whenever a tenant logs in, I will have to make sure the connection to the database changes dynamically. How will I accomplish this?

如何处理应用程序的身份验证?我的意思是,每当租户登录时,我必须确保与数据库的连接会动态变化。我该怎么做呢?

First of all, you need to consider how you're planning to distinguish each tenant. Most of the time I would see people tend to opt for subdomain. So in this case you need to check if the subdomain belongs to any of the user (by querying the main database) using a middleware and then connect to the database that belongs to the user.

首先,您需要考虑如何计划区分每个租户。大多数时候我都会看到人们倾向于选择子域名。因此,在这种情况下,您需要使用中间件检查子域是否属于任何用户(通过查询主数据库),然后连接到属于用户的数据库。

Tenanti doesn't manage that part of the process, because everyone has different style on that aspect, but we do provide a code to dynamically connect to your database tenant from a base database configuration.

Tenanti并没有管理这个过程的一部分,因为每个人在这方面都有不同的风格,但是我们确实提供了一个代码,可以从数据库配置中动态地连接到数据库租户。

Let say you have the following config:

假设您有以下配置:

<?php

return [
    'fetch' => PDO::FETCH_CLASS,
    'default' => 'primary',
    'connections' => [
        'primary' => [
            //
        ],
        'tenants' => [
                'driver'    => 'mysql',
                'host'      => 'dbhost',     // for user with id=1
                'username'  => 'dbusername', // for user with id=1
                'password'  => 'dbpassword', // for user with id=1
                'charset'   => 'utf8',
                'collation' => 'utf8_unicode_ci',
                'prefix'    => '',
                'strict'    => false,
            ],
       ],
    ],
    'migrations' => 'migrations',
    'redis' => [ ... ],
];

You can follow the step available in https://github.com/orchestral/tenanti#multi-database-connection-setup and add the following code.

您可以遵循https://github.com/orchestra /tenanti#multi-database- connec-setup中可用的步骤,并添加以下代码。

<?php namespace App\Providers;

use Orchestra\Support\Facades\Tenanti;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Tenanti::setupMultiDatabase('tenants', function (User $entity, array $template) {
            $template['database'] = "tenant_{$entity->getKey()}";

            return $template;
        });
    }
}

This would ensure that you be using tenant_1 database for user=1, tenant_2 database for user=2 and so on.

这将确保您对user=1使用tenant_1数据库,对user=2使用tenant_2数据库,等等。

So how does Tenanti detect which user if active?

This is where you need to add logic in your middleware.

这是您需要在中间件中添加逻辑的地方。

$user = App\User::whereSubdomain($request->route()->parameter('tenant'))->first();

Tenanti::driver('user')->asDefaultDatabase($user, 'tenants_{id}');

#2


5  

I've never used this package, but using the code you submitted above here's what I think is probably close to the right solution. You will probably still need to play with some of these values to get them correct:

我从来没有使用过这个包,但是使用您上面提交的代码,我认为这可能是正确的解决方案。你可能仍然需要使用其中的一些值来纠正它们:

Migration Paths

Since you're using the multi-database configuration, I believe you should be able to keep your migrations in the normal location, i.e. database/migrations. Tenanti will then create an exact replica of the database for each tenant in a different database. However, when you run php artisan tenanti:install user it might actually create a folder under database/ that indicates where you should put your migrations.

由于您正在使用多数据库配置,我认为您应该能够将迁移保持在正常位置,例如数据库/迁移。然后,Tenanti将为不同数据库中的每个承租者创建数据库的精确副本。但是,当您运行php artisan tenanti:安装用户时,它实际上可能会在数据库中创建一个文件夹,这表明您应该将迁移放在哪里。

What is a "driver"?

The driver describes whether Tenanti will use a single or multiple databases, what models to use for determining different tenants, and where to store migrations. It is what you identified in the Tenanti config file you used above.

驱动程序描述Tenanti将使用单个或多个数据库、用于确定不同租户的模型以及存储迁移的位置。它是您在上面使用的Tenanti配置文件中识别的。

Database Connection Selection

You need to update config/database.php as follows. In a normal Laravel app, you would have the DB connection setup as follows:

您需要更新配置/数据库。php如下。在一个普通的Laravel应用程序中,DB连接设置如下:

<?php
    return [
        'fetch' => PDO::FETCH_CLASS,
        'default' => env('DB_CONNECTION', 'mysql'),
        'connections' => [
            'sqlite' => [ ...DB connection info... ],
            'mysql'  => [ ...DB connection info... ],
            'pgsql'  => [ ...DB connection info... ],
            'sqlsrv' => [ ...DB connection info... ],
        ],
        'migrations' => 'migrations',
        'redis' => [ ... ],
    ];

However, in the case of Tenanti multi-database setup, you need to add in different connection info for each tenant's database. To do this you would add a new level to your database.php config file (this example assumes you're using mysql, but you could use any DB, or even different database engines for different tenants):

但是,在Tenanti多数据库设置的情况下,您需要为每个租户的数据库添加不同的连接信息。为此,您将向数据库添加一个新级别。php配置文件(本例假设您正在使用mysql,但是您可以为不同的租户使用任何DB甚至不同的数据库引擎):

<?php
    return [
        'fetch' => PDO::FETCH_CLASS,
        'default' => env('DB_CONNECTION', 'mysql'),
        'connections' => [
            'tenants' => [
                'user_1' => [
                    'driver'    => 'mysql',
                    'host'      => 'dbhost',     // for user with id=1
                    'database'  => 'dbname',     // for user with id=1
                    'username'  => 'dbusername', // for user with id=1
                    'password'  => 'dbpassword', // for user with id=1
                    'charset'   => 'utf8',
                    'collation' => 'utf8_unicode_ci',
                    'prefix'    => '',
                    'strict'    => false,
                ],
                'user_2' => [
                    'driver'    => 'mysql',
                    'host'      => 'dbhost',     // for user with id=2
                    'database'  => 'dbname',     // for user with id=2
                    'username'  => 'dbusername', // for user with id=2
                    'password'  => 'dbpassword', // for user with id=2
                    'charset'   => 'utf8',
                    'collation' => 'utf8_unicode_ci',
                    'prefix'    => '',
                    'strict'    => false,
                ],
           ],
        ],
        'migrations' => 'migrations',
        'redis' => [ ... ],
    ];

As you can see, each tenant has its own database instance that can be located on a different host and have a different username/password. Tenanti needs to be told how to figure out which database to use. This is what the documentation on Database Connection Resolver describes. In their example, they've named their tenant databases using acme_{$user->id} whereas in my example above I used user_{$user->id}.

如您所见,每个承租者都有自己的数据库实例,可以位于不同的主机上,并具有不同的用户名/密码。Tenanti需要被告知如何确定使用哪个数据库。这就是数据库连接解析器文档所描述的。在他们的示例中,他们使用acme_{$user->id}来命名他们的租户数据库,而在我的示例中,我使用了user_{$user->id}。

Like I said, I've never actually set this up myself, but these are my best guesses based on the docs, and having used other packages by this same developer. Hope this helps!

就像我说的,我从来没有自己设置过这个,但是这些是我基于文档的最好的猜测,并且使用了同一开发人员的其他包。希望这可以帮助!

#1


10  

+1 to @morphatic answer, it quiet accurate on most of the stuff.

+1到@形态回答,它对大多数东西都很准确。

Migration

One set of files is for the main DB which will store all the tenant information and the other files will be for the tenant DB. So how and where will these be stored?

一组文件用于主DB,主DB将存储所有租户信息,其他文件将用于租户DB。那么,如何以及在哪里存储这些数据呢?

For your main database you should be able to use the default database/migration and utilize php artisan make:migration and php artisan migrate.

对于主数据库,您应该能够使用默认的数据库/迁移并使用php artisan make:迁移和php artisan迁移。

Tenanti however will use the migration path set under the "driver" configuration. e.g:

不过,Tenanti将使用“驱动程序”配置下的迁移路径集。例句:

'path' => database_path('tenanti/user'),

In this case the migration will be created/migrated from database/tenanti/user (you can choose other folder and it will use that folder). Once you set this up you can create new migration file for the user tenant via php artisan tenanti:make user create_blogs_table (as an example) and run migration via php artisan tenanti:migrate user (see the similarity between Laravel migration command and Tenanti?).

在这种情况下,迁移将从数据库/tenanti/user创建/迁移(您可以选择其他文件夹,它将使用该文件夹)。一旦您设置了这个,您就可以通过php artisan tenanti为用户租户创建新的迁移文件:让用户create_blogs_table(作为示例)并通过php artisan tenanti运行迁移:迁移用户(查看Laravel迁移命令和tenanti之间的相似之处)。

Driver

Driver is just the grouping of a tenant, you maybe grouping it by users, companies, or team etc. And there is possibility that you may require more than one type of group per project, otherwise most of the time you only be using single "group" or "driver".

驱动程序只是一个租户的分组,您可能会将其分组为用户、公司或团队等。您可能需要多个类型的每个项目,否则大多数情况下您只使用单个“组”或“驱动程序”。

Authentication or Accessing DB

How will I handle the authentication for the application? I mean whenever a tenant logs in, I will have to make sure the connection to the database changes dynamically. How will I accomplish this?

如何处理应用程序的身份验证?我的意思是,每当租户登录时,我必须确保与数据库的连接会动态变化。我该怎么做呢?

First of all, you need to consider how you're planning to distinguish each tenant. Most of the time I would see people tend to opt for subdomain. So in this case you need to check if the subdomain belongs to any of the user (by querying the main database) using a middleware and then connect to the database that belongs to the user.

首先,您需要考虑如何计划区分每个租户。大多数时候我都会看到人们倾向于选择子域名。因此,在这种情况下,您需要使用中间件检查子域是否属于任何用户(通过查询主数据库),然后连接到属于用户的数据库。

Tenanti doesn't manage that part of the process, because everyone has different style on that aspect, but we do provide a code to dynamically connect to your database tenant from a base database configuration.

Tenanti并没有管理这个过程的一部分,因为每个人在这方面都有不同的风格,但是我们确实提供了一个代码,可以从数据库配置中动态地连接到数据库租户。

Let say you have the following config:

假设您有以下配置:

<?php

return [
    'fetch' => PDO::FETCH_CLASS,
    'default' => 'primary',
    'connections' => [
        'primary' => [
            //
        ],
        'tenants' => [
                'driver'    => 'mysql',
                'host'      => 'dbhost',     // for user with id=1
                'username'  => 'dbusername', // for user with id=1
                'password'  => 'dbpassword', // for user with id=1
                'charset'   => 'utf8',
                'collation' => 'utf8_unicode_ci',
                'prefix'    => '',
                'strict'    => false,
            ],
       ],
    ],
    'migrations' => 'migrations',
    'redis' => [ ... ],
];

You can follow the step available in https://github.com/orchestral/tenanti#multi-database-connection-setup and add the following code.

您可以遵循https://github.com/orchestra /tenanti#multi-database- connec-setup中可用的步骤,并添加以下代码。

<?php namespace App\Providers;

use Orchestra\Support\Facades\Tenanti;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Tenanti::setupMultiDatabase('tenants', function (User $entity, array $template) {
            $template['database'] = "tenant_{$entity->getKey()}";

            return $template;
        });
    }
}

This would ensure that you be using tenant_1 database for user=1, tenant_2 database for user=2 and so on.

这将确保您对user=1使用tenant_1数据库,对user=2使用tenant_2数据库,等等。

So how does Tenanti detect which user if active?

This is where you need to add logic in your middleware.

这是您需要在中间件中添加逻辑的地方。

$user = App\User::whereSubdomain($request->route()->parameter('tenant'))->first();

Tenanti::driver('user')->asDefaultDatabase($user, 'tenants_{id}');

#2


5  

I've never used this package, but using the code you submitted above here's what I think is probably close to the right solution. You will probably still need to play with some of these values to get them correct:

我从来没有使用过这个包,但是使用您上面提交的代码,我认为这可能是正确的解决方案。你可能仍然需要使用其中的一些值来纠正它们:

Migration Paths

Since you're using the multi-database configuration, I believe you should be able to keep your migrations in the normal location, i.e. database/migrations. Tenanti will then create an exact replica of the database for each tenant in a different database. However, when you run php artisan tenanti:install user it might actually create a folder under database/ that indicates where you should put your migrations.

由于您正在使用多数据库配置,我认为您应该能够将迁移保持在正常位置,例如数据库/迁移。然后,Tenanti将为不同数据库中的每个承租者创建数据库的精确副本。但是,当您运行php artisan tenanti:安装用户时,它实际上可能会在数据库中创建一个文件夹,这表明您应该将迁移放在哪里。

What is a "driver"?

The driver describes whether Tenanti will use a single or multiple databases, what models to use for determining different tenants, and where to store migrations. It is what you identified in the Tenanti config file you used above.

驱动程序描述Tenanti将使用单个或多个数据库、用于确定不同租户的模型以及存储迁移的位置。它是您在上面使用的Tenanti配置文件中识别的。

Database Connection Selection

You need to update config/database.php as follows. In a normal Laravel app, you would have the DB connection setup as follows:

您需要更新配置/数据库。php如下。在一个普通的Laravel应用程序中,DB连接设置如下:

<?php
    return [
        'fetch' => PDO::FETCH_CLASS,
        'default' => env('DB_CONNECTION', 'mysql'),
        'connections' => [
            'sqlite' => [ ...DB connection info... ],
            'mysql'  => [ ...DB connection info... ],
            'pgsql'  => [ ...DB connection info... ],
            'sqlsrv' => [ ...DB connection info... ],
        ],
        'migrations' => 'migrations',
        'redis' => [ ... ],
    ];

However, in the case of Tenanti multi-database setup, you need to add in different connection info for each tenant's database. To do this you would add a new level to your database.php config file (this example assumes you're using mysql, but you could use any DB, or even different database engines for different tenants):

但是,在Tenanti多数据库设置的情况下,您需要为每个租户的数据库添加不同的连接信息。为此,您将向数据库添加一个新级别。php配置文件(本例假设您正在使用mysql,但是您可以为不同的租户使用任何DB甚至不同的数据库引擎):

<?php
    return [
        'fetch' => PDO::FETCH_CLASS,
        'default' => env('DB_CONNECTION', 'mysql'),
        'connections' => [
            'tenants' => [
                'user_1' => [
                    'driver'    => 'mysql',
                    'host'      => 'dbhost',     // for user with id=1
                    'database'  => 'dbname',     // for user with id=1
                    'username'  => 'dbusername', // for user with id=1
                    'password'  => 'dbpassword', // for user with id=1
                    'charset'   => 'utf8',
                    'collation' => 'utf8_unicode_ci',
                    'prefix'    => '',
                    'strict'    => false,
                ],
                'user_2' => [
                    'driver'    => 'mysql',
                    'host'      => 'dbhost',     // for user with id=2
                    'database'  => 'dbname',     // for user with id=2
                    'username'  => 'dbusername', // for user with id=2
                    'password'  => 'dbpassword', // for user with id=2
                    'charset'   => 'utf8',
                    'collation' => 'utf8_unicode_ci',
                    'prefix'    => '',
                    'strict'    => false,
                ],
           ],
        ],
        'migrations' => 'migrations',
        'redis' => [ ... ],
    ];

As you can see, each tenant has its own database instance that can be located on a different host and have a different username/password. Tenanti needs to be told how to figure out which database to use. This is what the documentation on Database Connection Resolver describes. In their example, they've named their tenant databases using acme_{$user->id} whereas in my example above I used user_{$user->id}.

如您所见,每个承租者都有自己的数据库实例,可以位于不同的主机上,并具有不同的用户名/密码。Tenanti需要被告知如何确定使用哪个数据库。这就是数据库连接解析器文档所描述的。在他们的示例中,他们使用acme_{$user->id}来命名他们的租户数据库,而在我的示例中,我使用了user_{$user->id}。

Like I said, I've never actually set this up myself, but these are my best guesses based on the docs, and having used other packages by this same developer. Hope this helps!

就像我说的,我从来没有自己设置过这个,但是这些是我基于文档的最好的猜测,并且使用了同一开发人员的其他包。希望这可以帮助!