## Migration modular monolith DDD — Lesstime (0.1 → 3.3) Cette MR regroupe l'intégralité de la refonte en monolithe modulaire (strangler progressif, additif). Elle remplace les MR stackées de Phase 1 (#12–#16), désormais incluses ici. **Ne pas merger avant validation fonctionnelle** : branche destinée à être testée telle quelle. ### Périmètre — 9 modules sous `src/Module/` | Phase | Module | Contenu | |------|--------|---------| | 0.1 | (socle) | infrastructure modulaire, `ModuleInterface`, mapping Doctrine par module | | 0.2 | (socle front) | auto-détection des layers Nuxt sous `frontend/modules/*` | | 1.1 | **Core** | Identité (User/Auth), Notifications, Notifier | | 1.2 | Core | RBAC fin (permissions `module.resource.action`, sidebar gated) | | 1.3 | Core | Audit log (`#[Auditable]`, listener, provider DBAL) | | 2.1 | **TimeTracking** | TimeEntry + MCP + export | | 2.2 | **ProjectManagement** | cœur métier Projets/Tâches + 38 MCP tools | | 2.3 | **Absence** | demandes, soldes, policies, justificatifs | | 2.4 | **Directory** | Clients (migrés) + **Prospects** (nouveau, conversion → Client) | | 2.5 | **Mail** | intégration IMAP OVH + liens tâches | | 2.6 | **Integration** | Gitea / BookStack / Zimbra / Share | | 3.1 | **Reporting** | rapports transverses (DBAL read-only, 0 import inter-module) | | 3.2 | **ClientPortal** | portail client (ROLE_CLIENT cloisonné, tickets, notifications) | | 3.3 | (finition) | nettoyage legacy — `src/Entity` vide, app 100% modulaire | ### Architecture - Découplage inter-modules par **contrats** (`UserInterface`, `ProjectInterface`, `TaskInterface`, `TaskTagInterface`, `ClientInterface`, `ClientTicketInterface`, `LeaveProfileInterface`) + `resolve_target_entities` 100% modulaire (aucune cible legacy). - Repositories : interface `Domain/Repository` + implémentation `Infrastructure/Doctrine`, bindées. - Reporting en DBAL read-only pur (aucun import d'entité d'un autre module). - Chaque migration de module : déplacement à comportement préservé (API publique et noms d'outils MCP inchangés), migrations **additives** uniquement (zéro destructif). ### Sécurité - ROLE_CLIENT cloisonné : un utilisateur client n'accède qu'à `/portal` et à ses propres tickets (filtrés par `allowedProjects`), interdit sur toute l'API interne. - Correctif : interdiction pour un client de créer un lien vers le partage SMB (upload uniquement). ### QA non-régression (branche reconstruite from scratch) - Migrations from scratch + fixtures : OK. - Compilation dev + prod : OK. - **180 tests PHPUnit verts**, php-cs-fixer clean, ~96 routes, **66 outils MCP** tous sous `App\Module\*`. - Smoke test runtime multi-rôles (admin / ROLE_USER / ROLE_CLIENT) : 44 vérifications HTTP, **0 écart**, cloisonnement client étanche. - Build Nuxt OK, 9 layers, 0 import legacy résiduel. ### Points à arbitrer (hors périmètre de cette migration) - Durcissement MCP/IDOR pré-existant (`userId` explicite sans scoping sur certains tools TimeTracking/Absence/TaskDocument) — ticket dédié recommandé. - Validation fonctionnelle de **Prospect** et **ClientPortal** (conçus depuis les specs disque). - **Harmonisation visuelle Malio finale** (3.3) — finition esthétique inter-modules laissée au PO. --- ## ⚠️ Déploiement / migration des données — à ne pas oublier ### 1. Resynchroniser les séquences PostgreSQL après tout import/restore de dump Si la prod (ou tout environnement) est **montée depuis un dump** (`pg_restore` / `COPY`), les lignes sont chargées avec leurs `id` explicites **sans avancer les séquences** → au premier `INSERT` : `duplicate key value violates unique constraint "..._pkey"` (constaté en local sur `notification`, `task`, `time_entry`…). À lancer **juste après chaque restore/import** : ```sql DO $$ DECLARE r RECORD; maxid BIGINT; seq TEXT; BEGIN FOR r IN SELECT table_name, column_name FROM information_schema.columns WHERE table_schema='public' LOOP seq := pg_get_serial_sequence(quote_ident(r.table_name), r.column_name); IF seq IS NOT NULL THEN EXECUTE format('SELECT COALESCE(MAX(%I),0) FROM %I', r.column_name, r.table_name) INTO maxid; PERFORM setval(seq, GREATEST(maxid,1), maxid > 0); END IF; END LOOP; END $$; ``` > Ne concerne **pas** une prod qui tourne déjà (séquences avancées organiquement) — uniquement le cas restore/import. Idempotent, sans risque. ### 2. Fix dénormalisation des collections typées-contrat (code, inclus dans la branche) Les relations **to-many** typées par une interface `Shared\Domain\Contract\*` (`TimeEntry::tags` → `TaskTagInterface`, `Task::collaborators` → `UserInterface`) étaient **indénormalisables par API Platform** (mono-valué OK via IRI, collection KO) → **tout POST/PATCH portant une telle collection renvoyait 400/500**. Corrigé par un dénormaliseur générique `ContractRelationDenormalizer` (réutilise `resolve_target_entities`, zéro couplage par-entité) + test fonctionnel de non-régression. --------- Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #17
8.8 KiB
Roadmap — Migration Lesstime → modular monolith DDD (archi Starseed)
Plan de migration complet validé le 2026-06-19. Référence architecture : repo Starseed (
.claude/rules/*.md+ implémentation réelle). Détail technique du socle : voir2026-06-19-lst-56-modular-monolith-design.md.
Principes directeurs
- Strangler progressif : legacy (
src/Entity/…) et modules (src/Module/…) coexistent ; l'app reste fonctionnelle etmake testvert à chaque merge. Aucune migration destructive (prod Docker, BDD peuplée → migrations additives nullable uniquement). - DDD pragmatique (= Starseed réel) : ORM attrs dans l'entité Domain, Repository interface (Domain)
- impl Doctrine (Infra), Provider/Processor API Platform, contrats
Shared/Domain/Contractpour le cross-module. Pas de CQRS bus, pas de multi-tenant.
- impl Doctrine (Infra), Provider/Processor API Platform, contrats
- Tranches verticales : chaque module de Phase 2 est livré back + front (layer Malio) + MCP d'un coup → fonctionnel de bout en bout à son merge. L'ancienne idée d'un « ticket refonte front » global est dissoute : chaque module arrive déjà en Malio ; un ticket de finition harmonise à la fin.
- Ordre par dépendances : socle → Core (identité/RBAC/audit) → modules métier → transverse/finition.
- Zéro import inter-modules : interfaces
Shared/Domain/Contract+resolve_target_entities, ou domain events / contratNotifierInterface.
Garde-fous Starseed (appliqués à chaque entité migrée)
declare(strict_types=1) · TimestampableBlamableTrait (4 colonnes nullable) + subscriber ·
pagination obligatoire · COMMENT ON COLUMN (helper ColumnCommentsCatalog) ·
#[Auditable]/#[AuditIgnore] (dès que 1.3 est livré) · front Malio* + usePaginatedList +
useFormErrors · RBAC module.resource.action (dès 1.2).
Phase 0 — Socle (fondations, ne touche aucun métier)
0.1 · Socle back — infrastructure modulaire (réécrit depuis #56)
Dépend de : —
src/Shared/Domain/Contract/ (UserInterface, UserResolverInterface, ClientInterface, NotifierInterface),
Shared/Domain/Event/DomainEventInterface, Shared/Domain/Trait/TimestampableBlamableTrait,
Shared/Infrastructure/Doctrine/TimestampableBlamableSubscriber,
Shared/Infrastructure/Database/ColumnCommentsCatalog,
Shared/Infrastructure/ApiPlatform/{Resource,State} (ModulesResource/ModulesProvider,
SidebarResource/SidebarProvider), config/modules.php, config/sidebar.php, /api/version aligné.
Config additive : mapping Doctrine module prêt, migrations_paths modulaire, api_platform.mapping.paths.
AC : /api/modules + /api/sidebar répondent ; app verte ; aucune migration destructive.
0.2 · Socle front — shell + auto-détection des layers
Dépend de : 0.1
frontend/app/ (shell layouts/default.vue), frontend/shared/ (useApi déplacé, useSidebar,
useModules, stores), middlewares auth.global.ts + modules.global.ts, auto-détection des layers
modules/*/ dans nuxt.config.ts. Aucune page métier déplacée (app encore plate).
AC : sidebar dynamique depuis /api/sidebar ; routes désactivées redirigées ; app verte.
Phase 1 — Module Core (identité, sécurité, traçabilité — transverse)
1.1 · Core — Identité & Notifications
Dépend de : 0.1, 0.2
Migrer User + Auth/JWT dans src/Module/Core/ (Domain/Entity, Repository interface + Doctrine impl,
MeProvider, password hasher), User implements UserInterface, resolve_target_entities → Core\User.
Notification exposée via NotifierInterface. CoreModule.php (REQUIRED=true). Front : layer
modules/core/ (login, profile, admin users).
AC : login/JWT OK ; app verte ; aucun import direct App\Entity\User hors Core.
1.2 · RBAC fin (réécrit depuis #57)
Dépend de : 1.1
Role/Permission, permissions() par module, commande app:sync-permissions, PermissionVoter,
SidebarProvider filtrant par permission (en plus du module actif), seed RBAC. Front : gestion des
rôles + usePermissions.
AC : permissions module.resource.action ; sidebar gated par permission.
1.3 · Audit log (réécrit depuis #61)
Dépend de : 1.1
#[Auditable]/#[AuditIgnore] (Shared/Domain/Attribute), table audit_log (migration additive +
COMMENT ON COLUMN), AuditListener/AuditLogWriter/RequestIdProvider, AuditLogResource +
/api/audit-logs paginé/filtrable, page front + labels i18n audit.entity.*.
AC : CRUD des entités #[Auditable] tracé ; endpoint paginé ; aucune migration destructive.
Phase 2 — Modules métier (tranches verticales back + front + MCP, strangler)
2.1 · Module TimeTracking (premier module — rodage)
Dépend de : 1.1
Migrer TimeEntry → src/Module/TimeTracking/ (Domain/Entity, repo, ActiveTimeEntryProvider,
TimeEntryExportService/controller, MCP TimeEntry tools), front layer modules/time-tracking/
(time-tracking.vue, components, services, store timer). Timestampable additif. Rode toute la
mécanique modulaire à risque quasi nul.
AC : time tracking fonctionnel en module ; activation/désactivation testée ; app verte.
2.2 · Module ProjectManagement (cœur métier — réécrit depuis #56 pilote)
Dépend de : 2.1, 1.1
Project, Task, Workflow, TaskStatus, TaskGroup, TaskEffort, TaskPriority, TaskTag, TaskRecurrence, TaskDocument → src/Module/ProjectManagement/ (vertical back + MCP Task/Project/TaskMeta/Workflow +
front layer modules/project-management/). User/Client via contrats (Client encore legacy jusqu'à 2.4).
Notifications via NotifierInterface. #[ApiResource] conservés (étendre le scan). Timestampable additif.
AC : cœur en module sans régression API ; app verte.
2.3 · Module Absence
Dépend de : 1.1
AbsenceRequest/AbsencePolicy/AbsenceBalance + services (AbsenceBalanceService, AbsenceDayCalculator,
PublicHolidayProvider) + controllers (calendar, preview, justificatif) + MCP absence tools →
src/Module/Absence/, front layer modules/absence/.
AC : module absences complet ; app verte.
2.4 · Module Directory — Clients + Prospects (réécrit depuis #58)
Dépend de : 1.1 (et après 2.2 qui référence Client via contrat)
Client → src/Module/Directory/ + nouvelle entité Prospect. L'impl de ClientInterface migre du
legacy vers le module (resolve_target_entities mis à jour). Front répertoire (clients + prospects).
AC : Clients + Prospects en module ; contrats à jour ; app verte.
2.5 · Module Mail
Dépend de : 1.1, 2.2 (TaskMailLink → Task)
Mail* + TaskMailLink + MailSyncService + controllers + settings → src/Module/Mail/, front layer.
Intègre le WIP feat/mail-integration.
AC : mail en module ; app verte.
2.6 · Module Integration — Gitea / BookStack / Zimbra / Share
Dépend de : 1.1, 2.2 (liens Task)
Configs + services API (GiteaApiService, BookStackApiService, CalDavService, Share) + controllers +
liens → src/Module/Integration/, front (onglets admin + sections task).
AC : intégrations en module ; app verte.
Phase 3 — Transverse & finition
3.1 · Module Reporting (réécrit depuis #59)
Dépend de : Phase 2 (consomme les modules)
Reporting natif transverse (agrège time tracking, tâches, absences) via contrats / API. Module
src/Module/Reporting/ + front.
AC : rapports natifs ; aucune dépendance directe inter-modules.
3.2 · Module Portail client
Dépend de : 1.1, 2.2, 2.4
Portail client (accès restreint), module src/Module/ClientPortal/ + front layer + RBAC dédié.
AC : portail fonctionnel ; gated RBAC.
3.3 · Finition Malio + nettoyage legacy (réécrit depuis #60)
Dépend de : tout
Harmonisation visuelle Malio finale, vidage de src/Entity/ legacy résiduel, suppression du mapping
Doctrine legacy + des pages plates frontend/pages/ résiduelles, durcissement resolve_target_entities.
AC : src/Entity vide ; 100 % modulaire ; app verte ; aucune route/legacy orpheline.
Ordre d'exécution recommandé
0.1 → 0.2 → 1.1 → 1.2 → 1.3 → 2.1 → 2.2 → 2.3 → 2.4 → 2.5 → 2.6 → 3.1 → 3.2 → 3.3
Les tickets 1.2 et 1.3 peuvent se paralléliser après 1.1. Les modules 2.3 (Absence) et 2.4 (Directory) peuvent se paralléliser après 2.2. Mail (2.5) et Integration (2.6) suivent 2.2.
Mapping avec les tickets Lesstime existants
| Ancien | Devient |
|---|---|
| #56 (1/5 Aligner archi) | 0.1 Socle back (le reste éclaté en 0.2 + 2.2) |
| #57 (2/5 RBAC) | 1.2 RBAC fin |
| #58 (3/5 Répertoire) | 2.4 Directory |
| #59 (4/5 Reporting) | 3.1 Reporting |
| #60 (5/5 Front Malio) | 3.3 Finition Malio + nettoyage (le front se fait par module) |
| #61 (Audit) | 1.3 Audit log |
| (créés) | 0.2, 1.1, 2.1, 2.2, 2.3, 2.5, 2.6, 3.2 |