Tenant — клиент SaaS: компания, команда или магазин. Мультитенантность — одно приложение и одна кодовая база обслуживают многих tenant, и каждый видит только свои данные.
Три подхода
| Тема | Одна база | База на tenant |
|---|---|---|
| Изоляция | логическая (scope) | физическая |
| Миграции | один раз | для каждого tenant |
| Много tenant | легко | растёт число баз |
| Бэкап | общий | по tenant |
| Отчёты | просто | между tenant сложно |
💡 Начните с одной базы и
tenant_id. К базе на tenant переходите, только когда этого требуют договор, регулятор или очень крупный клиент.
Одна база: tenant_id и global scope
Храните найденного tenant в одном небольшом объекте. Зарегистрируйте его как scoped, чтобы он сбрасывался на каждый запрос и job.
namespace App\Support;
use App\Models\Tenant;
use RuntimeException;
final class CurrentTenant
{
private ?Tenant $tenant = null;
public function set(Tenant $tenant): void
{
$this->tenant = $tenant;
}
public function id(): int
{
return $this->tenant?->id
?? throw new RuntimeException('No tenant resolved');
}
}
// AppServiceProvider::register()
$this->app->scoped(CurrentTenant::class);namespace App\Models\Scopes;
use App\Support\CurrentTenant;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class TenantScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
$builder->where(
$model->getTable().'.tenant_id',
app(CurrentTenant::class)->id(),
);
}
}namespace App\Models\Concerns;
trait BelongsToTenant
{
protected static function bootBelongsToTenant(): void
{
static::addGlobalScope(new TenantScope);