365 lines
14 KiB
Markdown
365 lines
14 KiB
Markdown
# 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<string[]>('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/<Nom>/` 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.
|