Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

Pourquoi un état de l'art

Le Model-Driven Engineering n'a pas commencé avec cette série. Il a quarante ans, deux générations d'outils, des thèses et des produits, des succès académiques rarement déployés et des produits déployés rarement académiques. Avant de proposer un cadre — même design in public — la première honnêteté est de regarder ce qui existe, ce qu'on en garde, ce qu'on en jette, et pourquoi. Les sept outils traités ici ne sont pas tous opérationnels en 2026, mais chacun a contribué une idée que le cadre M3 décrit dans cette série reprend ou rejette explicitement.

L'ordre est chronologique avec un infléchissement : les six premiers sont issus de la tradition MDE académique (Eclipse, OMG, JetBrains) ; le septième, Langium, marque le retour TypeScript-natif après une décennie de relative dormance.

ATL — la matrice de rules + helpers (2003)

L'Atlas Transformation Language est né à l'INRIA dans l'équipe AtlanMod de Frédéric Jouault et Ivan Kurtev. C'est probablement l'outil MDE le plus utilisé en pratique, à la fois parce qu'il a été l'un des premiers à proposer une syntaxe vraiment lisible et parce qu'il a été massivement repris dans des cours universitaires européens. L'idée est simple : une transformation est une suite de rules et de helpers, séparée en un fichier .atl, qui prend un metamodel source et un metamodel cible.

Le coup de génie d'ATL est la typologie des rules : matched (s'applique automatiquement à tout élément source qui passe son guard), lazy (déclenchée à la demande, quand un autre élément y fait référence), called (appelée comme une procédure, prend des paramètres, peut être impérative). Cette distinction adresse un problème réel des transformations : selon le cas, on veut une projection systématique ou une projection à la demande. ATL a aussi posé que les helpers sont side-effect free et cacheables — ils opèrent sur des modèles source en lecture seule, leur résultat est mémoïsable. C'est un design qui anticipe de quinze ans la notion de pipeline incrémental Roslyn.

Ce que le cadre M3 décrit dans cette série garde d'ATL est substantiel : le typage kind: 'matched' | 'lazy' | 'called' sur les Rule, l'existence des Helper comme primitive de premier ordre, l'immutabilité des modèles source. Ce qu'il ne garde pas : la syntaxe DSL externe (.atl), la dépendance à EMF/Ecore, et le confinement Eclipse — choix qui ont fait d'ATL un outil exclusivement académique malgré ses qualités intrinsèques. L'autre absence notable est l'absence de traces first-class dans ATL ; il faut compiler vers ATL/EMFTVM pour les obtenir.

QVT — la stratification en trois langages (2008)

Le standard OMG MOF Query/View/Transformation tient en 230 pages PDF que personne ne lit en entier mais qui structure tacitement toute la conversation MDE depuis. Le coup intellectuel est la stratification : QVT n'est pas un langage mais trois, choisis en fonction du style de transformation qu'on écrit. Relations est déclaratif et bidirectionnel — on décrit quelles relations doivent tenir entre source et cible, le moteur résout. Operational est impératif et unidirectionnel — on décrit comment projeter. Core est le sous-ensemble minimal sur lequel Relations se traduit, conçu pour la sémantique formelle plutôt que pour l'écriture humaine.

L'autre apport de QVT, plus discret mais plus durable, est de promouvoir les traces au rang de citoyen de première classe. Une transformation QVT-Relations produit des traces explicites entre éléments source et éléments cible. C'est ce qui rend la bidirectionnalité possible (sans traces, pas de propagation arrière des modifications) et c'est aussi ce qui rend le débogage praticable. Aucune transformation industrielle non-triviale ne devrait être un trou noir.

Ce que le cadre M3 garde de QVT : les traces explicites, intégrées au TransformContext et inscrites dans le code émis sous forme de commentaire // @trace ruleId=… source=… target=…. Chaque ligne générée doit pouvoir remonter à sa rule et à son élément source — c'est non-négociable. Ce qu'il ne garde pas : la bidirectionnalité. La promesse de QVT-Relations (modifier la cible, propager vers la source automatiquement) n'a jamais tenu en pratique sur des transformations non-surjectives. Sur du M2C (texte généré), elle n'a même pas de sens : on n'édite pas le code émis. Le cadre M3 assume cette unidirectionnalité.

Acceleo / MOFM2T — les templates pour le M2C (2009)

Acceleo implémente partiellement le standard OMG MOFM2T (MOF Model to Text). Là où ATL et QVT s'occupent de M2M, Acceleo s'occupe de M2C — émission de texte depuis un modèle. La syntaxe est templating à la JSP avec des tags [template], [file], [for], [if]. On déclare un template paramétré par un élément de modèle, on l'invoque depuis un autre template, on accumule les sorties dans des balises [file] qui correspondent aux fichiers à émettre.

Acceleo a deux idées robustes. La première est la balise [file] : décider explicitement, dans le template, où va le contenu généré. C'est exactement ce que VirtFs.addSource({ relPath, contents }) propose dans ts-codegen-pipeline, sous une autre forme. La seconde est le post-processing des templates — trim, format, normalisation — appliqué après émission mais avant écriture disque. C'est ce qui empêche les fichiers générés d'être illisibles par accident d'indentation.

Ce que le cadre M3 garde d'Acceleo : la séparation émission → post-processing → commit disque, qui dans cette série passe par VirtFs.addSource puis runFixpoint puis commit atomique. Ce qu'il ne garde pas : la syntaxe de templating externe. En TypeScript de 2026, les template literals (backticks avec interpolation) suffisent à toutes les émissions sans qu'on ait besoin d'inventer une nouvelle syntaxe. C'est l'un des bénéfices nets de la convergence TS/JS : on peut écrire le générateur dans la même langue que le généré, sans changement de palette mentale.

Epsilon — l'architecture en couches (2008)

Epsilon est l'œuvre patiente de Dimitris Kolovos et de son équipe à York. Là où ATL est un outil ponctuel, Epsilon est une plateforme : un langage de base (EOL — Epsilon Object Language), et au-dessus, plusieurs langages task-specific qui en héritent — ETL pour les transformations, EGL pour le code generation, EVL pour la validation, ECL pour la comparaison, EML pour le merge, EPL pour le pattern matching. Tous extendent EOL, ce qui veut dire que la syntaxe d'expression est partagée et qu'un développeur qui connaît un sous-langage est productif dans les autres.

Le coup d'Epsilon est cette architecture en couches, qu'aucun autre outil MDE n'a réussi à appliquer aussi proprement. Une transformation ETL utilise les expressions EOL. Une validation EVL aussi. Une émission EGL aussi. Le code de base est factorisé une fois, ré-exploité partout. C'est l'équivalent MDE de l'AST partagé entre les phases d'un compilateur.

L'autre apport durable d'Epsilon est les guards sur les rules : un prédicat optionnel qui filtre l'applicabilité. Une matched rule ETL a une source, un target, et un guard (un EOL expression ou un bloc). Si le guard renvoie false, la rule ne s'applique pas. Cela évite la dégénérescence en multiplication de matched-rules quasi-identiques et garde la grammaire compacte.

Ce que le cadre M3 garde d'Epsilon : les guards sur Rule, typés Guard<TFrom> avec une seule méthode evaluate(source: TFrom): boolean. Ce qu'il garde aussi, à un niveau structurel : l'idée qu'un cadre unique (M3) supporte plusieurs préoccupations — transformation, validation, émission — par extension typée plutôt que par outils séparés. Ce qu'il ne garde pas : la syntaxe externe, et la dépendance forte à EMF qu'Epsilon a héritée de son environnement Eclipse.

JetBrains MPS — éditer dans le langage de sortie (2009)

Meta Programming System est probablement la chose la plus radicale jamais tentée en MDE industriel. MPS abandonne le texte. Tout est arbre de syntaxe abstraite, édité directement. Vous n'écrivez pas du code, vous projetez l'AST dans une vue qui ressemble à du code mais qui n'est pas du texte. Conséquence : pas de parser, pas de grammaire à maintenir, pas de problèmes d'ambiguïté. La projection visuelle peut être tabulaire, hiérarchique, mathématique, mixte.

Le coup conceptuel de MPS, pour cette série, est ailleurs : c'est sa prise au sérieux du multi-stage. La documentation MPS dit littéralement que le processus de transformation peut impliquer de nombreux modèles intermédiaires et résulte ultimement dans un modèle de sortie dans un langage dont la sémantique est déjà définie ailleurs. C'est une définition opérationnelle du critère d'arrêt d'une chaîne de transformations : on s'arrête quand on tombe dans un langage qu'on n'a plus besoin de traduire. JavaScript ES2022 par exemple ; ou TypeScript compilé en JS. Cette série emprunte ce critère implicitement : le M2C s'arrête au TypeScript, parce que TypeScript a sa propre sémantique (le langage est défini ailleurs), et qu'au-delà c'est tsc qui prend le relais.

L'autre apport MPS est que ses templates de générateur sont écrits dans le langage de sortie. Le template d'un générateur Java est lui-même du Java (avec annotations spéciales pour les variables). Cela maximise le confort éditorial et minimise le saut mental. Le cadre M3 décrit ici fait pareil, par circonstance : on génère du TypeScript depuis du TypeScript, sans changer de langue.

Ce que le cadre M3 ne garde pas : la projection éditoriale. MPS demande son propre éditeur, et c'est ce qui l'a empêché de prendre dans le monde TypeScript où VS Code est l'écosystème dominant. Le cadre M3 décrit ici suppose que l'éditeur des M1 est le même que l'éditeur des emit — VS Code éditant des fichiers .ts. C'est une régression par rapport à MPS, assumée.

Roslyn IIncrementalGenerator — le pipeline déclaratif (.NET 6, 2021)

Roslyn a longtemps proposé ISourceGenerator, qui était une interface impérative : on définissait une méthode Execute(GeneratorExecutionContext) qui scannait tout, comparait tout, émettait tout. Le verdict après deux ans : trop lent à grande échelle, parce que le scan exhaustif s'amplifiait avec la taille de la solution. La réponse de Microsoft en .NET 6 a été IIncrementalGenerator, qui change le modèle mental : on ne définit plus un Execute ; on définit un pipeline dataflow déclaratif dans Initialize(IncrementalGeneratorInitializationContext).

Le pipeline a des nœuds : SyntaxProvider (re-tourne quand un fichier syntaxiquement pertinent change), CompilationProvider (re-tourne quand la compilation globale change), AdditionalTextsProvider (re-tourne quand un fichier additionnel change). On compose ces sources avec Where, Select, Combine, Collect. Roslyn mémoïse chaque nœud : si les inputs n'ont pas changé, le nœud n'est pas réexécuté, son output cached est réutilisé. Sur une solution moyenne, cela transforme un générateur de plusieurs secondes en générateur de plusieurs millisecondes.

Le construct le plus émerveillant est ForAttributeWithMetadataName : Roslyn peut skip entièrement la phase de scan pour les fichiers qui ne contiennent pas l'attribut tracké. C'est une optimisation O(1) par fichier non-pertinent, là où l'ancien ISourceGenerator était O(N).

Ce que le cadre M3 garde de Roslyn : la forme dataflow déclarative, héritée par @frenchexdev/ts-codegen-pipeline. On définit des stages, on les ordonne, on laisse le moteur convergeer au fixpoint. La mémoïsation par stage est la propriété qui rend le tout incrémental. Le cadre M3 décrit dans cette série hérite donc indirectement de cette philosophie via ts-codegen-pipeline, qui en est l'incarnation TypeScript.

Langium — TypeScript-natif, LSP de base (2022)

Langium est l'œuvre de TypeFox, la même équipe qui a maintenu Xtext pendant quinze ans. Langium est Xtext refait pour TypeScript. La grammaire est déclarée dans un fichier .langium, Langium génère un parser, un AST type-safe, un Language Server Protocol intégré qui pilote VS Code (et n'importe quel éditeur LSP-compatible). Tout est TypeScript, tourne dans Node, et s'installe avec npm install.

L'apport principal de Langium pour le cadre M3 est négatif — c'est ce qu'il fait que le cadre M3 décrit ici ne fait pas. Langium part d'une grammaire textuelle : on définit un DSL avec sa syntaxe, son parser, son AST. Le cadre M3, lui, part de TypeScript-as-source : on annote des classes avec des décorateurs et ts-morph ou l'API TypeScript Compiler lit l'AST natif. Pas de grammaire à inventer. Pas de parser à maintenir. Pas de coloration syntaxique custom à intégrer dans VS Code — TypeScript apporte déjà la sienne.

Les deux approches sont complémentaires. Langium est le bon choix quand le DSL doit être lu par des non-développeurs (une syntaxe métier dédiée). Le cadre M3 est le bon choix quand le DSL est consommé par d'autres TypeScript et que le M1 doit s'intégrer dans la chaîne de typage. Cette série assume la seconde voie, sans prétendre disqualifier la première.

Ce que le cadre M3 ne reprend pas : le LSP intégré. Langium fournit gratuitement l'autocomplétion, la navigation Go to Definition, le hover documentation, le refactoring. Le cadre M3 décrit ici hérite de l'expérience TypeScript native (VS Code + TypeScript Language Service), qui est excellente pour les classes décorées mais ne sait pas qu'un @AggregateRoot interdit certains patterns. Combler ce gap demanderait un TypeScript language plugin spécialisé — c'est une perspective ouverte, pas un objet de cette série.

Megamodel et orchestration — ProMoTA, MAPLE (2010s-2020s)

À côté des outils proprement dits, une littérature s'est développée autour de l'orchestration de chaînes de transformations. ProMoTA propose un enactment environment qui prend un modèle de processus, le compile en une chaîne de transformations, et l'oriente avec un megamodel — un modèle dont les éléments sont eux-mêmes des modèles, des metamodèles et des transformations. ProMoTA va jusqu'à proposer une Global Trace Map qui consolide les traces locales de chaque transformation en une trace globale traversant toute la chaîne.

Le concept de megamodel n'a pas pris dans la pratique industrielle, sans doute parce que les outils qui le manipulent (MAPLE, Papyrus) demandent un investissement Eclipse considérable pour des projets qui se contenteraient d'un script shell. Mais l'idée est conceptuellement juste : on a besoin d'un objet qui réifie la chaîne complète. Sans cela, l'orchestration vit dans la documentation, dans le package.json, dans le Makefile — éparpillée, donc à jamais cassée.

Ce que le cadre M3 garde du megamodel : la réification de la chaîne dans un objet Megamodel typé, qui agrège schemas, compositions, transformations, emitters, requirements, features, acceptance criteria, quality gates, et un chain: ReadonlyArray<string> d'ids ordonnés. C'est lui qu'un futur runtime exécutera, et c'est lui qu'une revue de code interrogera quand un membre de l'équipe demandera où passe le requirement REQ-OPENSTORE-B2B-001.

Synthèse — la matrice des emprunts

Outil Année Apport pour le cadre M3 Statut dans le cadre
ATL 2003 Typologie matched/lazy/called ; Helper cacheables ; immutabilité source Repris : Rule.kind, Helper.pure: true
QVT 2008 Traces first-class entre source/cible Repris : Trace, inscrit en commentaire @trace ; bidirectionnel non repris
Acceleo / MOFM2T 2009 Balise [file] ; post-processing avant commit Repris implicitement via VirtFs.addSource + commit atomique
Epsilon 2008 Architecture en couches (EOL → ETL/EGL/EVL) ; guards first-class Repris : Guard<TFrom> ; architecture en couches transposée au M3
JetBrains MPS 2009 Multi-stage explicite ; critère d'arrêt (langage déjà sémantiquement défini) ; templates dans le langage de sortie Repris : multi-stage via ts-codegen-pipeline ; éditeur projectionnel non repris
Roslyn IIncrementalGenerator 2021 Pipeline dataflow déclaratif ; mémoïsation par stage Repris (indirectement) via ts-codegen-pipeline
Langium 2022 TypeScript-natif ; LSP intégré Complémentaire (grammaire textuelle) ; le cadre M3 prend la voie TypeScript-as-source
ProMoTA / Megamodel 2010s Réification de la chaîne complète ; Global Trace Map Repris : Megamodel typé avec chain: ReadonlyArray<string>
⬇ Download