# Architecture Modulaire Monolith — MALIO ## Contexte Projet monolith Symfony API Platform (back) + Nuxt (front dans un dossier `frontend/`). L'objectif est une architecture **modular monolith DDD** permettant de vendre des modules indépendamment à chaque client (ex : un client achète GestionRH + Formation, un autre GestionRH + Paie + Pointage). --- ## Principes fondamentaux 1. **Chaque module est un bounded context autonome** — son propre Domain, Application, Infrastructure. 2. **Communication inter-modules uniquement par events ou contrats** — jamais d'import direct d'une entité d'un autre module. 3. **Les modules sont activables/désactivables** par tenant sans casser le reste. 4. **Le module `Core` est obligatoire** — il gère users, tenants, auth. 5. **Le dossier `Shared/`** contient le noyau technique commun (interfaces, value objects de base, bus). 6. **API Platform** : les `#[ApiResource]` sont sur des classes Resource dédiées dans `Infrastructure/ApiPlatform/Resource/`, jamais directement sur les entités du Domain. 7. **CQRS** : Command/Query handlers dans la couche Application, DTO d'entrée/sortie découplés des entités. 8. **Multi-tenant natif** : chaque entité porte un `tenantId`. --- ## Structure cible — Backend (`src/`) ``` src/ ├── Kernel.php │ ├── Shared/ │ ├── Domain/ │ │ ├── ValueObject/ │ │ │ ├── AggregateId.php │ │ │ └── Email.php │ │ ├── Event/ │ │ │ └── DomainEventInterface.php │ │ └── Contract/ ← Interfaces inter-modules │ │ ├── UserResolverInterface.php │ │ └── TenantAwareInterface.php │ ├── Application/ │ │ └── Bus/ │ │ ├── CommandBusInterface.php │ │ └── QueryBusInterface.php │ └── Infrastructure/ │ ├── Doctrine/ │ ├── Messenger/ │ └── ApiPlatform/ │ └── OpenApi/ │ ├── Module/ │ ├── Core/ ← Module obligatoire │ │ ├── Domain/ │ │ │ ├── Entity/ │ │ │ │ ├── User.php │ │ │ │ └── Tenant.php │ │ │ ├── Repository/ │ │ │ │ └── UserRepositoryInterface.php │ │ │ └── Event/ │ │ │ └── UserCreated.php │ │ ├── Application/ │ │ │ ├── Command/ │ │ │ │ ├── CreateUser.php │ │ │ │ └── CreateUserHandler.php │ │ │ ├── Query/ │ │ │ │ ├── GetUserById.php │ │ │ │ └── GetUserByIdHandler.php │ │ │ └── DTO/ │ │ │ └── UserOutput.php │ │ ├── Infrastructure/ │ │ │ ├── Doctrine/ │ │ │ │ ├── DoctrineUserRepository.php │ │ │ │ └── mapping/ │ │ │ └── ApiPlatform/ │ │ │ ├── Resource/ │ │ │ │ └── UserResource.php │ │ │ ├── State/ │ │ │ │ ├── Provider/ │ │ │ │ └── Processor/ │ │ │ └── Filter/ │ │ └── CoreModule.php ← Déclaration : config, routes, dépendances │ │ │ ├── GestionRH/ ← Module vendable │ │ ├── Domain/ │ │ │ ├── Entity/ │ │ │ │ ├── Employee.php │ │ │ │ ├── Contract.php │ │ │ │ └── Leave.php │ │ │ ├── ValueObject/ │ │ │ ├── Repository/ │ │ │ │ └── EmployeeRepositoryInterface.php │ │ │ ├── Event/ │ │ │ │ └── EmployeeHired.php │ │ │ ├── Exception/ │ │ │ └── Service/ │ │ ├── Application/ │ │ │ ├── Command/ │ │ │ ├── Query/ │ │ │ ├── DTO/ │ │ │ └── Listener/ ← Réagit aux events d'autres modules │ │ ├── Infrastructure/ │ │ │ ├── Doctrine/ │ │ │ │ ├── DoctrineEmployeeRepository.php │ │ │ │ ├── mapping/ │ │ │ │ └── migrations/ ← Migrations propres au module │ │ │ └── ApiPlatform/ │ │ │ ├── Resource/ │ │ │ │ └── EmployeeResource.php │ │ │ └── State/ │ │ │ ├── Provider/ │ │ │ └── Processor/ │ │ └── GestionRHModule.php │ │ │ ├── Formation/ ← Module vendable │ │ ├── Domain/ │ │ ├── Application/ │ │ ├── Infrastructure/ │ │ └── FormationModule.php │ │ │ ├── Pointage/ ← Module vendable │ │ ├── Domain/ │ │ ├── Application/ │ │ ├── Infrastructure/ │ │ └── PointageModule.php │ │ │ └── Paie/ ← Module vendable │ ├── Domain/ │ ├── Application/ │ ├── Infrastructure/ │ └── PaieModule.php │ └── config/ └── modules.php ← Liste des modules activés ``` --- ## Structure cible — Frontend (`frontend/`) ``` frontend/ ├── app/ │ ├── layouts/ │ │ └── default.vue ← Menu dynamique selon modules activés │ ├── middleware/ │ │ └── modules.global.ts ← Bloque les routes de modules désactivés │ └── app.vue │ ├── shared/ │ ├── composables/ │ │ ├── useAuth.ts │ │ ├── useApi.ts │ │ ├── useTenant.ts │ │ └── useModules.ts ← Expose les modules activés (via API) │ ├── components/ │ │ ├── ui/ ← Design system (boutons, tables, modals…) │ │ ├── AppSidebar.vue │ │ └── AppHeader.vue │ ├── types/ │ │ └── index.ts │ ├── utils/ │ └── stores/ │ ├── auth.ts │ └── tenant.ts │ ├── modules/ │ ├── core/ │ │ ├── pages/ │ │ │ ├── login.vue │ │ │ ├── dashboard.vue │ │ │ └── users/ │ │ │ ├── index.vue │ │ │ └── [id].vue │ │ ├── components/ │ │ ├── composables/ │ │ ├── stores/ │ │ │ └── users.ts │ │ ├── types/ │ │ └── core.module.ts │ │ │ ├── gestion-rh/ │ │ ├── pages/ │ │ │ ├── employees/ │ │ │ │ ├── index.vue │ │ │ │ └── [id].vue │ │ │ ├── contracts/ │ │ │ └── leaves/ │ │ ├── components/ │ │ │ ├── EmployeeCard.vue │ │ │ └── LeaveCalendar.vue │ │ ├── composables/ │ │ │ └── useEmployees.ts │ │ ├── stores/ │ │ │ └── employees.ts │ │ ├── types/ │ │ │ └── index.ts │ │ └── gestion-rh.module.ts │ │ │ ├── formation/ │ │ ├── pages/ │ │ ├── components/ │ │ ├── composables/ │ │ ├── stores/ │ │ ├── types/ │ │ └── formation.module.ts │ │ │ ├── pointage/ │ │ ├── pages/ │ │ ├── components/ │ │ ├── composables/ │ │ ├── stores/ │ │ ├── types/ │ │ └── pointage.module.ts │ │ │ └── paie/ │ ├── pages/ │ ├── components/ │ ├── composables/ │ ├── stores/ │ ├── types/ │ └── paie.module.ts │ ├── plugins/ │ └── modules-loader.ts ← Charge dynamiquement les modules activés ├── nuxt.config.ts └── package.json ``` --- ## Règles d'implémentation ### Backend #### 1. Communication inter-modules - **INTERDIT** : `use Module\GestionRH\Domain\Entity\Employee` depuis le module Paie. - **AUTORISÉ** : passer par `Shared\Domain\Contract\EmployeeResolverInterface` ou écouter un domain event comme `EmployeeHired`. - Les contrats (interfaces) partagés vivent dans `Shared/Domain/Contract/`. #### 2. Couche Domain (aucune dépendance Symfony) - Entités avec logique métier encapsulée (pas d'anemic model). - Value Objects pour la validation (Email, Money, OrderStatus…). - Repository = interface uniquement. - Domain Events pour notifier les autres modules. - Aucun `use Symfony\...` dans ce dossier. #### 3. Couche Application - CQRS : Command (écriture) + Query (lecture) avec leurs Handlers. - Les Handlers orchestrent : ils appellent le Domain et les interfaces Repository. - DTO Input/Output pour le contrat d'entrée/sortie, découplés des entités. - Listeners pour réagir aux events d'autres modules. #### 4. Couche Infrastructure - Implémentations Doctrine des repositories. - Mapping et migrations **propres à chaque module** (pas de migration centralisée). - API Platform : - `Resource/` : classes avec `#[ApiResource]`, jamais posé sur les entités Domain. - `State/Provider/` : fournisseurs de données (GET). - `State/Processor/` : traitement des mutations (POST/PUT/PATCH/DELETE), délègue au bus ou aux handlers. - `Filter/` : filtres API Platform spécifiques au module. - Les endpoints n'apparaissent dans l'OpenAPI que si le module est activé. #### 5. Module declaration (`*Module.php`) Chaque module déclare : - Son identifiant unique. - Ses dépendances (ex : GestionRH dépend de Core). - Sa configuration de services. - Ses routes. #### 6. Activation/désactivation - Fichier `config/modules.php` ou variable d'environnement listant les modules actifs. - Le Kernel ne charge que les services/routes/migrations des modules activés. - Endpoint API : `GET /api/tenant/modules` retourne la liste des modules activés pour le tenant courant. #### 7. Multi-tenant - Chaque entité porte un `tenantId`. - Filtrage automatique Doctrine par tenant (Doctrine Filter ou listeners). ### Frontend #### 1. Déclaration de module (`*.module.ts`) ```typescript export default defineAppModule({ id: 'gestion-rh', label: 'Gestion RH', icon: 'i-lucide-users', requiredModules: ['core'], navigation: [ { label: 'Employés', to: '/employees', icon: 'i-lucide-user' }, { label: 'Contrats', to: '/contracts', icon: 'i-lucide-file-text' }, { label: 'Congés', to: '/leaves', icon: 'i-lucide-calendar' }, ], permissions: ['employee.read', 'employee.write', 'leave.manage'], }) ``` #### 2. Chargement dynamique (`useModules`) ```typescript export const useModules = () => { const enabledModules = useState('modules', () => []) const isEnabled = (moduleId: string) => enabledModules.value.includes(moduleId) return { enabledModules, isEnabled } } ``` Au boot de l'app, appel `GET /api/tenant/modules` pour récupérer les modules activés. #### 3. Middleware de protection des routes ```typescript export default defineNuxtRouteMiddleware((to) => { const { isEnabled } = useModules() const moduleId = resolveModuleFromRoute(to.path) if (moduleId && !isEnabled(moduleId)) { return navigateTo('/dashboard') } }) ``` #### 4. Sidebar dynamique Le layout `default.vue` itère sur les modules activés et affiche leurs entrées `navigation`. #### 5. Isolation - Chaque module front a ses propres pages, components, composables, stores, types. - Les composants partagés (design system) sont dans `shared/components/ui/`. - Un module front ne doit jamais importer depuis un autre module front. Si besoin de données croisées, passer par l'API ou par un composable partagé dans `shared/`. --- ## Résumé des conventions de nommage | Élément | Convention | Exemple | |---------|-----------|---------| | Module back | PascalCase | `Module/GestionRH/` | | Module front | kebab-case | `modules/gestion-rh/` | | Entity | PascalCase singulier | `Employee.php` | | Repository interface | `*RepositoryInterface` | `EmployeeRepositoryInterface.php` | | Repository impl | `Doctrine*Repository` | `DoctrineEmployeeRepository.php` | | Command | Verbe + Nom | `CreateEmployee.php` | | Command Handler | `*Handler` | `CreateEmployeeHandler.php` | | DTO | `*Input` / `*Output` | `EmployeeOutput.php` | | API Resource | `*Resource` | `EmployeeResource.php` | | Provider | `*Provider` | `EmployeeProvider.php` | | Processor | `*Processor` | `CreateEmployeeProcessor.php` | | Module declaration back | `*Module.php` | `GestionRHModule.php` | | Module declaration front | `*.module.ts` | `gestion-rh.module.ts` | | Composable | `use*` | `useEmployees.ts` | | Store | nom du domaine | `employees.ts` | --- ## Checklist de migration Si le projet existe déjà avec une structure plate, voici l'ordre de migration recommandé : 1. Créer `Shared/` et y déplacer les interfaces/VO de base. 2. Créer `Module/Core/` et y migrer users, auth, tenants. 3. Pour chaque futur module vendable, créer le dossier `Module//` avec les 3 couches (Domain, Application, Infrastructure). 4. Déplacer les entités, repositories, services dans le bon module. 5. Remplacer les imports directs inter-modules par des contrats (`Shared/Domain/Contract/`) ou des events. 6. Isoler les migrations Doctrine par module. 7. Adapter les resources API Platform (les sortir des entités, créer les Providers/Processors). 8. Côté front, créer `shared/` et `modules/`, migrer les pages/composants dans le bon module. 9. Implémenter `useModules` + middleware de routes + sidebar dynamique. 10. Tester l'activation/désactivation d'un module de bout en bout.