Files
Lesstime/docs/superpowers/specs/2026-06-19-migration-modular-monolith-roadmap.md
T
matthieu 8313c759c6
Auto Tag Develop / tag (push) Successful in 9s
Migration modular monolith DDD (0.1 → 3.3) (#17)
## 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
2026-06-23 13:50:42 +00:00

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 : voir 2026-06-19-lst-56-modular-monolith-design.md.

Principes directeurs

  • Strangler progressif : legacy (src/Entity/…) et modules (src/Module/…) coexistent ; l'app reste fonctionnelle et make test vert à 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/Contract pour le cross-module. Pas de CQRS bus, pas de multi-tenant.
  • 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 / contrat NotifierInterface.

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 TimeEntrysrc/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, TaskDocumentsrc/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) Clientsrc/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