Implementare Multi-Tenancy in Laravel

La flessibilità del framework Laravel ci consente di implementare anche una architettura multi-tenancy.
La multi-tenancy è un’architettura software in cui una singola istanza di un’applicazione serve più applicazioni cliente.
In questo post, esploreremo il concetto di multitenancy in Laravel, i suoi benefici e come implementarlo. Vedremo anche le configurazioni necessarie, esempi pratici di codice e i passaggi per garantire che l’applicazione multi-tenant funzioni senza problemi.
Ogni tenant può essere considerato come un cliente separato con i propri dati isolati da altri tenant.
- Che cosa è il Multi-Tenancy?
- I vantaggi del modello
- Implementazione di MultiTenancy Laravel
- Passaggio 1: installare il pacchetto
- Passaggio 2: pubblicare la configurazione
- Passaggio 3: Impostare il Tenancy
- Passaggio 4: creare il modello di Tenant
- Passaggio 5: Identificazione del tenant
- Passaggio 6: Configurare il Middleware
- Passaggio 7: Migration dei Tenant
- Passaggio 8: Migrazioni del Tenant
- Passaggio 9: Seed del Tenant
- Scenari pratici
Estimated reading time: 5 minuti
Che cosa è il Multi-Tenancy?
La multi-tenancy è un’architettura che consente a una singola applicazione di ospitare più tenant (clienti) con i loro dati e configurazioni. I dati di ogni tenant sono isolati dagli altri, garantendo la riservatezza e la sicurezza dei dati. La multi-tenancy può essere implementata in vari modi, tra cui:
- Singolo database, Schema condiviso: tutti i
tenant
condividono lo stesso database e le stesse tabelle. - Database singolo, Schemi separati: i
tenant
condividono lo stesso database ma hanno il proprio schema. - Database multipli: ogni tenant ha il proprio database.
I vantaggi del modello
- Scalabilità: più facile scalare l’applicazione per i nuovi
tenant
senza grandi cambiamenti. - Efficienza dei costi: riduce i costi delle infrastrutture e della manutenzione condividendo le risorse tra i
tenant
. - Manutenzione centralizzata: semplifica gli aggiornamenti e la manutenzione in quanto c’è solo un’istanza dell’applicazione.
- Esperienza utente migliorata: ambienti personalizzabili per diversi
tenant
.
Implementazione di MultiTenancy Laravel
Sono stati sviluppati diversi tool per Laravel, per aiutare a implementare architetture multi-tenancy, come:
hyn/multi-tenant
: consente di gestire un’architettura software in cui un’unica istanza di un’applicazione serve più tenant (o account utente), garantendo la sicurezza e la riservatezza dei dati per ciascun gruppo di utenti. Questo tool è spesso utilizzato per fornire servizi SaaS (Software-as-a-Service).tenancy/tenancy
: soluzione flessibile per il multitenancy Laravel. Vediamo qui di seguito alcune caratteristiche:- Automatic Tenancy: Invece di modificare la configurazione del tuo progetto, il pacchetto avvia automaticamente il multi-tenancy in background. Cambia le connessioni al database, separa le cache, aggiunge prefissi ai filesystem e altro ancora;
- Separazione automatica dei dati: Il pacchetto rende automaticamente tenant-aware: database, cache, filesystem, code e store Redis. Non devi modificare nulla se hai già scritto il tuo progetto;
- Integrazione con altri pacchetti: Poiché la modalità automatica cambia la connessione al database predefinita, molti altri pacchetti utilizzeranno questa connessione. Ad esempio, puoi utilizzare Laravel Nova all’interno dell’applicazione tenant per gestire le risorse del tenant;
- Architettura basata su eventi: La logica di avvio del multi-tenancy, la logica post-creazione del tenant e altre operazioni avvengono grazie agli eventi. Puoi personalizzare ogni aspetto;
- Multi-database tenancy: Se non vuoi utilizzare l’approccio database-per-tenant, il pacchetto offre modelli per limitare i modelli al tenant corrente, anche per modelli non direttamente correlati al tenant;
In questo articolo useremo il tool tenancy/tenancy
, che fornisce una solida base per la multitenancy in Laravel. Vediamo quindi passo per passo come implementare la multitenancy in Laravel.
Passaggio 1: installare il pacchetto
Per prima cosa, installiamo il pacchetto tenancy/tenancy
con il composer:
composer require tenancy/tenancy
Passaggio 2: pubblicare la configurazione
Procediamo ora a pubblicare i file di configurazione del pacchetto:
php artisan vendor:publish --tag=tenancy
Passaggio 3: Impostare il Tenancy
Procediamo ora a configurare il file tenancy.php
, che si trova nella directory config
, per soddisfare le esigenze applicative.
Quindi seguito ci sono alcuni parametri:
- storage_driver, storage: puoi configurare il driver per lo storage dei tenant. Ad esempio, puoi scegliere tra Redis o un database. Leggi di più sulla pagina dei driver di storage.
- tenant_route_namespace: Imposta il namespace del controller utilizzato per le rotte in
routes/tenant.php
. - exempt_domains: Se un hostname presente in questo array viene visitato, le rotte in
tenant.php
non verranno registrate, consentendoti di utilizzare le stesse rotte presenti in quel file. - database: Imposta la connessione al database per i dati del tenant.
- redis: Configura la connessione Redis per i dati del tenant.
- cache: Sostituisce l’istanza di
CacheManager
per supportare il multi-tenancy. - filesystem: Configura il percorso di archiviazione dei file per i tenant.
Per una guida completa leggi la documentazione.
Passaggio 4: creare il modello di Tenant
Creare un modello di Tenant
per rappresentare ogni tenant nella tua applicazione:
php artisan make:model Tenant -m
Nel file di migrazione, definire la struttura della tabella tenants
:
Schema::create('tenants', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('domain')->unique();
$table->timestamps();
});
Passaggio 5: Identificazione del tenant
Identificare il tenant in base al dominio o al sottodominio. Aggiorniamo quindi il modello del Tenant
per includere la logica di identificazione del tenant:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Tenancy\Identification\Contracts\Tenant as TenantContract;
use Tenancy\Affects\Connections\Contracts\ProvidesDatabase;
class Tenant extends Model implements TenantContract, ProvidesDatabase
{
protected $fillable = ['name', 'domain'];
public function getTenantKey(): string
{
return 'domain';
}
public function getConnectionName(): string
{
return 'tenant';
}
public function configureDatabase($event): void
{
$event->useConnection('tenant', [
'database' => 'tenant_' . $this->id,
]);
}
}
Quindi i metodi sono tre, getTenantKey
per identificare il dominio, getConnectionName
per identificare la connessione e configureDatabase
per impostare la connessione al DB corretto.
Passaggio 6: Configurare il Middleware
Creiamo il middleware che si occuperà di cambiate la connessione del database in base al tenant:
php artisan make:middleware TenantMiddleware
Nel middleware, identifichiamo il tenant e impostiamo la connessione al database:
namespace App\Http\Middleware; use Closure; use Tenancy\Identification\Contracts\Tenant; class TenantMiddleware { public function handle($request, Closure $next) { $hostname = $request->getHost(); $tenant = Tenant::where('domain', $hostname)->first(); if ($tenant) { tenant($tenant); } return $next($request); } }
Quindi registriamo il middleware TenantMiddleware
in app/Http/Kernel.php
:
protected $middlewareGroups = [
'web' => [
// other middleware...
\App\Http\Middleware\TenantMiddleware::class,
],
];
Passaggio 7: Migration
dei Tenant
Creare migrazioni specifiche per tenant specificando la connessione del tenant:
php artisan make:migration create_posts_table --create=posts --path=database/migrations/tenant
Nel file di migrazione, definire la struttura della tabella del tenant:
Schema::connection('tenant')->create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->timestamps();
});
Passaggio 8: Migrazioni del Tenant
Creare un comando per eseguire migrazioni specifiche del tenant:
php artisan make:command MigrateTenants
Nel file di comando, eseguire le migrazioni per ogni tenant:
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Tenant;
class MigrateTenants extends Command
{
protected $signature = 'migrate:tenants';
protected $description = 'Run migrations for all tenants';
public function handle()
{
Tenant::all()->each(function (Tenant $tenant) {
tenant($tenant);
$this->call('migrate', [
'--database' => 'tenant',
'--path' => 'database/migrations/tenant',
'--force' => true,
]);
});
$this->info('Tenant migrations completed successfully.');
}
}
Passaggio 9: Seed
del Tenant
Creare un seeder per seminare i dati specifici del tenant:
php artisan make:seeder TenantSeeder
Nel seminatore, definire i dati da seminare:
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Tenant;
class TenantSeeder extends Seeder
{
public function run()
{
Tenant::create([
'name' => 'Tenant One',
'domain' => 'tenant1.example.com',
]);
Tenant::create([
'name' => 'Tenant Two',
'domain' => 'tenant2.example.com',
]);
}
}
Esegui il sider:
php artisan db:seed --class=TenantSeeder
Scenari pratici
Applicazioni SaaS
La multi-tenancy è ideale per le applicazioni SaaS in cui più clienti (tenants) devono utilizzare la stessa applicazione mantenendo i loro dati separati.
Grandi organizzazioni
Le organizzazioni con più reparti possono utilizzare multi-tenancy per gestire i dati dipartimentali separatamente all’interno della stessa applicazione.
Hosting gestito
I provider di hosting possono utilizzare la multi-tenancy per offrire servizi gestiti a più clienti con ambienti isolati.