feat: Optimiser la pagination avec cache-aside et ports de lecture dédiés

Les listes paginées (utilisateurs, classes, matières, affectations,
invitations parents, droits à l'image) effectuaient des requêtes SQL
complètes à chaque chargement de page, sans aucun cache. Sur les
établissements avec plusieurs centaines d'enregistrements, cela causait
des temps de réponse perceptibles et une charge inutile sur PostgreSQL.

Cette refactorisation introduit un cache tag-aware (Redis en prod,
filesystem en dev) avec invalidation événementielle, et extrait les
requêtes de lecture dans des ports Application / implémentations DBAL
conformes à l'architecture hexagonale. Un middleware Messenger garantit
l'invalidation synchrone du cache même pour les événements routés en
asynchrone (envoi d'emails), évitant ainsi toute donnée périmée côté UI.
This commit is contained in:
2026-03-01 14:33:56 +01:00
parent ce05207c64
commit 23dd7177f2
41 changed files with 2854 additions and 1584 deletions

View File

@@ -39,6 +39,12 @@ framework:
adapter: cache.adapter.filesystem
default_lifetime: 604800 # 7 jours
# Pool dédié au cache des requêtes paginées (1h TTL, tag-aware)
paginated_queries.cache:
adapter: cache.adapter.filesystem
default_lifetime: 3600 # 1 heure
tags: true
# Test environment uses Redis to avoid filesystem cache timing issues in E2E tests
# (CLI creates tokens, FrankenPHP must see them immediately)
when@test:
@@ -73,6 +79,10 @@ when@test:
adapter: cache.adapter.redis
provider: '%env(REDIS_URL)%'
default_lifetime: 604800
paginated_queries.cache:
adapter: cache.adapter.redis_tag_aware
provider: '%env(REDIS_URL)%'
default_lifetime: 3600
when@prod:
framework:
@@ -110,3 +120,7 @@ when@prod:
adapter: cache.adapter.redis
provider: '%env(REDIS_URL)%'
default_lifetime: 604800 # 7 jours
paginated_queries.cache:
adapter: cache.adapter.redis_tag_aware
provider: '%env(REDIS_URL)%'
default_lifetime: 3600 # 1 heure

View File

@@ -25,6 +25,7 @@ framework:
middleware:
- App\Shared\Infrastructure\Messenger\AddCorrelationIdStampMiddleware
- App\Shared\Infrastructure\Messenger\CorrelationIdMiddleware
- App\Administration\Infrastructure\Middleware\PaginatedCacheInvalidationMiddleware
- App\Shared\Infrastructure\Messenger\MessengerMetricsMiddleware
transports:

View File

@@ -25,6 +25,8 @@ services:
Psr\Cache\CacheItemPoolInterface $sessionsCache: '@sessions.cache'
# Bind student guardians cache pool (no TTL - persistent data)
Psr\Cache\CacheItemPoolInterface $studentGuardiansCache: '@student_guardians.cache'
# Bind paginated queries cache pool (1h TTL, tag-aware)
Symfony\Contracts\Cache\TagAwareCacheInterface $paginatedQueriesCache: '@paginated_queries.cache'
# Bind named message buses
Symfony\Component\Messenger\MessageBusInterface $eventBus: '@event.bus'
Symfony\Component\Messenger\MessageBusInterface $commandBus: '@command.bus'
@@ -237,6 +239,25 @@ services:
App\Administration\Domain\Repository\StudentGuardianRepository:
alias: App\Administration\Infrastructure\Persistence\Cache\CacheStudentGuardianRepository
# Paginated Read Model Ports
App\Administration\Application\Port\PaginatedUsersReader:
alias: App\Administration\Infrastructure\ReadModel\DbalPaginatedUsersReader
App\Administration\Application\Port\PaginatedClassesReader:
alias: App\Administration\Infrastructure\ReadModel\DbalPaginatedClassesReader
App\Administration\Application\Port\PaginatedSubjectsReader:
alias: App\Administration\Infrastructure\ReadModel\DbalPaginatedSubjectsReader
App\Administration\Application\Port\PaginatedAssignmentsReader:
alias: App\Administration\Infrastructure\ReadModel\DbalPaginatedAssignmentsReader
App\Administration\Application\Port\PaginatedParentInvitationsReader:
alias: App\Administration\Infrastructure\ReadModel\DbalPaginatedParentInvitationsReader
App\Administration\Application\Port\PaginatedStudentImageRightsReader:
alias: App\Administration\Infrastructure\ReadModel\DbalPaginatedStudentImageRightsReader
# GradeExistenceChecker (stub until Notes module exists)
App\Administration\Application\Port\GradeExistenceChecker:
alias: App\Administration\Infrastructure\Service\NoOpGradeExistenceChecker