feat: Infrastructure multi-tenant avec isolation par sous-domaine
Une application SaaS éducative nécessite une séparation stricte des données entre établissements scolaires. L'architecture multi-tenant par sous-domaine (ecole-alpha.classeo.local) permet cette isolation tout en utilisant une base de code unique. Le choix d'une résolution basée sur les sous-domaines plutôt que sur des headers ou tokens facilite le routage au niveau infrastructure (reverse proxy) et offre une UX plus naturelle où chaque école accède à "son" URL dédiée.
This commit is contained in:
18
backend/config/bundles.php
Normal file
18
backend/config/bundles.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
||||
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
|
||||
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
||||
];
|
||||
5
backend/config/packages/debug.yaml
Normal file
5
backend/config/packages/debug.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
when@dev:
|
||||
debug:
|
||||
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
|
||||
# See the "server:dump" command to start a new server.
|
||||
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
|
||||
18
backend/config/packages/dev/tenant.yaml
Normal file
18
backend/config/packages/dev/tenant.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
# Tenants de développement
|
||||
# Ces tenants sont automatiquement chargés en environnement dev
|
||||
|
||||
parameters:
|
||||
tenant.dev_configs:
|
||||
- tenantId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
||||
subdomain: 'ecole-alpha'
|
||||
databaseUrl: '%env(DATABASE_URL)%'
|
||||
- tenantId: 'b2c3d4e5-f6a7-8901-bcde-f12345678901'
|
||||
subdomain: 'ecole-beta'
|
||||
databaseUrl: '%env(DATABASE_URL)%'
|
||||
|
||||
services:
|
||||
App\Shared\Infrastructure\Tenant\TenantRegistry:
|
||||
class: App\Shared\Infrastructure\Tenant\InMemoryTenantRegistry
|
||||
factory: ['@App\Shared\Infrastructure\Tenant\TenantRegistryFactory', 'createFromConfig']
|
||||
arguments:
|
||||
$configs: '%tenant.dev_configs%'
|
||||
19
backend/config/packages/prod/tenant.yaml
Normal file
19
backend/config/packages/prod/tenant.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
# Configuration des tenants en production
|
||||
#
|
||||
# En production, les tenants peuvent être configurés de deux façons :
|
||||
# 1. Via la variable d'environnement TENANT_CONFIGS (JSON)
|
||||
# 2. Via une implémentation DatabaseTenantRegistry (à implémenter)
|
||||
#
|
||||
# Pour l'instant, on utilise InMemoryTenantRegistry avec configuration env.
|
||||
# Si aucun tenant n'est configuré, toutes les requêtes retourneront 404.
|
||||
|
||||
parameters:
|
||||
# Format JSON attendu: [{"tenantId":"uuid","subdomain":"ecole","databaseUrl":"postgres://..."}]
|
||||
tenant.prod_configs_json: '%env(default::TENANT_CONFIGS)%'
|
||||
|
||||
services:
|
||||
App\Shared\Infrastructure\Tenant\TenantRegistry:
|
||||
class: App\Shared\Infrastructure\Tenant\InMemoryTenantRegistry
|
||||
factory: ['@App\Shared\Infrastructure\Tenant\TenantRegistryFactory', 'createFromEnv']
|
||||
arguments:
|
||||
$configsJson: '%tenant.prod_configs_json%'
|
||||
3
backend/config/packages/property_info.yaml
Normal file
3
backend/config/packages/property_info.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
framework:
|
||||
property_info:
|
||||
with_constructor_extractor: true
|
||||
10
backend/config/packages/routing.yaml
Normal file
10
backend/config/packages/routing.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
framework:
|
||||
router:
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
default_uri: '%env(DEFAULT_URI)%'
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
router:
|
||||
strict_requirements: null
|
||||
9
backend/config/packages/tenant.yaml
Normal file
9
backend/config/packages/tenant.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
# Tenant infrastructure event subscribers
|
||||
App\Shared\Infrastructure\Tenant\TenantMiddleware:
|
||||
tags:
|
||||
- { name: kernel.event_subscriber }
|
||||
|
||||
App\Shared\Infrastructure\Security\TenantAccessDeniedHandler:
|
||||
tags:
|
||||
- { name: kernel.event_subscriber }
|
||||
18
backend/config/packages/test/tenant.yaml
Normal file
18
backend/config/packages/test/tenant.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
# Tenants pour les tests
|
||||
# Utilise les mêmes tenants que dev pour les tests d'intégration
|
||||
|
||||
parameters:
|
||||
tenant.test_configs:
|
||||
- tenantId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
||||
subdomain: 'ecole-alpha'
|
||||
databaseUrl: '%env(DATABASE_URL)%'
|
||||
- tenantId: 'b2c3d4e5-f6a7-8901-bcde-f12345678901'
|
||||
subdomain: 'ecole-beta'
|
||||
databaseUrl: '%env(DATABASE_URL)%'
|
||||
|
||||
services:
|
||||
App\Shared\Infrastructure\Tenant\TenantRegistry:
|
||||
class: App\Shared\Infrastructure\Tenant\InMemoryTenantRegistry
|
||||
factory: ['@App\Shared\Infrastructure\Tenant\TenantRegistryFactory', 'createFromConfig']
|
||||
arguments:
|
||||
$configs: '%tenant.test_configs%'
|
||||
13
backend/config/packages/web_profiler.yaml
Normal file
13
backend/config/packages/web_profiler.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
|
||||
framework:
|
||||
profiler:
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
profiler:
|
||||
collect: false
|
||||
collect_serializer_data: true
|
||||
4
backend/config/routes/framework.yaml
Normal file
4
backend/config/routes/framework.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
when@dev:
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
||||
prefix: /_error
|
||||
3
backend/config/routes/security.yaml
Normal file
3
backend/config/routes/security.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
_security_logout:
|
||||
resource: security.route_loader.logout
|
||||
type: service
|
||||
8
backend/config/routes/web_profiler.yaml
Normal file
8
backend/config/routes/web_profiler.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
|
||||
prefix: /_profiler
|
||||
@@ -4,6 +4,7 @@
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
parameters:
|
||||
tenant.base_domain: '%env(TENANT_BASE_DOMAIN)%'
|
||||
|
||||
services:
|
||||
# default configuration for services in this file
|
||||
@@ -25,3 +26,20 @@ services:
|
||||
# Domain services need to be registered explicitly to avoid framework coupling
|
||||
# Example: App\Administration\Application\Command\:
|
||||
# resource: '../src/Administration/Application/Command/'
|
||||
|
||||
# Tenant services
|
||||
App\Shared\Infrastructure\Tenant\TenantResolver:
|
||||
arguments:
|
||||
$baseDomain: '%tenant.base_domain%'
|
||||
|
||||
# TenantRegistry est configuré par environnement :
|
||||
# - dev: config/packages/dev/tenant.yaml (tenants de test)
|
||||
# - prod: à configurer via admin ou env vars
|
||||
|
||||
App\Shared\Infrastructure\Tenant\Command\CreateTenantDatabaseCommand:
|
||||
arguments:
|
||||
$masterDatabaseUrl: '%env(DATABASE_URL)%'
|
||||
|
||||
App\Shared\Infrastructure\Tenant\Command\TenantMigrateCommand:
|
||||
arguments:
|
||||
$projectDir: '%kernel.project_dir%'
|
||||
|
||||
Reference in New Issue
Block a user