No desenvolvimento de aplicações SaaS (Software as a Service), o conceito de Multitenancy (Multilocação) é fundamental. Ele se refere a uma arquitetura onde uma única instância de um software atende a vários clientes (tenants). O grande desafio técnico reside em como isolar os dados de cada cliente de forma segura e eficiente no banco de dados.
Existem três abordagens principais para implementar essa segregação:
1. Database per Tenant (Isolamento Total) Link para o cabeçalho
Cada cliente possui seu próprio banco de dados físico. É a forma mais segura de isolamento.
-
Vantagens: Segurança máxima, facilidade para restaurar backups individuais e escalabilidade vertical personalizada.
-
Desvantagens: Alto custo operacional e dificuldade para atualizar o esquema (migrates) em centenas de bancos simultaneamente.
2. Schema per Tenant (Isolamento Lógico) Link para o cabeçalho
Os clientes compartilham o mesmo banco de dados, mas cada um tem seu próprio Schema (como ocorre no PostgreSQL ou instâncias separadas no SQL Server).
- Vantagens: Equilíbrio entre segurança e custo. É mais fácil de gerenciar que múltiplos bancos, mas mantém uma barreira lógica clara.
* Desvantagens: Ainda exige gestão de múltiplas migrações de esquema.
3. Shared Database, Shared Schema (Isolamento por Linha) Link para o cabeçalho
Todos os dados de todos os clientes residem na mesma tabela. A diferenciação é feita através de uma coluna identificadora (ex: tenant_id).
-
Vantagens: Menor custo e extrema facilidade para manutenção e atualizações globais.
-
Desvantagens: Risco de “vazamento” de dados se uma consulta esquecer o filtro do ID, e o banco de dados pode se tornar gigantesco rapidamente.
Exemplo Prático: Implementando Shared Schema (PHP/Laravel) Link para o cabeçalho
Uma das formas mais didáticas de ver o Multitenancy em ação é através do Global Scopes. No Laravel, podemos garantir que qualquer consulta à tabela de produtos filtre automaticamente pelo cliente logado.
1. A Migração da Tabela: Link para o cabeçalho
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id'); // O divisor de águas
$table->string('name');
$table->decimal('price', 10, 2);
$table->timestamps();
});
2. O Global Scope (A Camada de Segurança): Link para o cabeçalho
Este código garante que, internamente, o SQL executado sempre contenha um WHERE tenant_id = X.
namespace App\Models\Scopes;
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)
{
// Supõe-se que o ID do tenant está na sessão ou resolvido pelo domínio
if (session()->has('tenant_id')) {
$builder->where('tenant_id', session('tenant_id'));
}
}
}
3. No Model: Link para o cabeçalho
protected static function booted()
{
static::addGlobalScope(new TenantScope);
}
Com isso, ao executar Product::all(), o sistema retornará apenas os produtos do cliente atual, sem que o desenvolvedor precise escrever o filtro manualmente em cada parte do código.