Tags

,

Récemment, j’expliquais ce que pouvais donner le scaffolding appliqué au MDSD. Voici maintenant un exemple de scaffolding “aveugle” prenant en entrée un modèle UML et implémenté à l’aide du langage de transformation modèle à modèle ATL. Nous utilisons la toute dernière version 3.0.0 d’ATL sortie avec Eclipse Galileo.

Voici un diagramme présentant la vue d’ensemble:

scaffolding-aveugle

Nous reprenons le cas décrit dans les slides de l’article précédent, une architecture 3-Tiers classique avec les concepts d’Entity et de DAO modélisés avec le langage UML. Voici un diagramme du modèle UML d’entrée. Le diagramme a été créé avec UML2Tools qui fait partie de la release train Galileo:

three-tiers-uml1

Voici le modèle UML attendu en sortie:

three-tiers-uml-scaffolded

Il s’agit ici de scaffolding “aveugle” pour reprendre la taxonomie proposée précédemment. Il est aveugle parce que les éléments du modèle d’entrée ne peuvent pas “voir” les éléments scaffoldés comme l’illustre le diagramme suivant:

Scaffolding "Aveugle" 2

Le scaffolding est ici généré de manière statique avec le langage de transformation modèle à modèle ATL. La transformation que nous allons définir peut se résumer ainsi:

Pour chaque Entity qui n’a pas de DAO associé, créer un nouveau DAO et l’associer à l’Entity

Il nous faut pour cela pouvoir faire la distinction entre les classes qui sont des entités et celles qui sont des DAO. Pour faire simple, nous n’utilisons pas de stéréotypes. Nous nous basons uniquement sur le nom de la classe. Par convention, nous considérons donc qu’un DAO est une classe UML dont le nom se termine par ‘Dao’. A l’inverse, nous considérons qu’une Entity est une Classe UML dont le nom ne finit pas par ‘Dao’. Comme notre exemple n’utilise que les concepts d’Entity et de DAO, il n’y aura pas d’ambiguité, mais pour un cas réel bien entendu, cette convention serait trop simpliste et il pourrait être convenu d’utiliser des stéréotypes.

Dans le langage UML chaque élément vient avec un certain nombre de propriétés et de méthodes pré-définies mais aucune ne permet de savoir si une Class est un DAO ou une Entity. C’est une règle métier, une convention propre à notre cas d’utilisation. Il nous faut donc enrichir le langage dans le contexte de notre transformation pour y ajouter les notions de DAO et d’Entity. Avec ATL, des règles métiers peuvent être ajoutées à l’aide de helpers. Presque tous les langages de transformation modèle à modèle ont un équivalent permettant d’enrichir le comportement d’un modèle dans le contexte de la transformation. Ils ne sont par contre pas compatibles. C’est un niveau d’interropérabilité qu’il serait fort intéréssant d’avoir, mais je reviendrais là dessus plus tard.

Nous définissons donc deux helper permettant de savoir pour une Class donnée s’il s’agit d’un DAO ou d’une Entity:

helper context UML!Class def : isDao : Boolean = self.name->endsWith('Dao');
helper context UML!Class def : isEntity : Boolean = not self.name->endsWith('Dao');

Une fois ces helper définis, il viennent enrichir la liste des propriétés disponibles pour une Class dans le contexte de notre transformation.

Toujours par convention, nous considérons ici qu’un DAO est affecté à une Entité lorsqu’il existe un lien d’usage depuis le DAO vers l’entité. Il nous faut donc pouvoir récupérer la liste des liens d’usage pointant vers une Class et sélectionner ceux dont la source est un DAO.

Nous commençons par récupérer la liste des liens d’usage pointant vers une entité, pour ceci, nous définissons le helper suivant:

helper context UML!Class def : usages : Sequence(UML!Usage) =
	let allUsages : Sequence(UML!Usage) = UML!Usage.allInstances()->asSequence() in
	allUsages->select(u|u.supplier->exists(s|s=self));

ATL utilise une syntaxe très proche d’OCL pour interroger le modèle d’entrée. Ici, nous commençons par récupérer tous les Usage définis dans le modèle d’entrée sous forme de Sequence OCL. En gros, une Sequence est une liste. Puis nous ne retenons (select) que les liens d’usage pour lesquels notre entité (self) est contenue (exists) dans la liste des destinataires (supplier) du lien d’usage. A noter que supplier est au singulier dans UML mais il s’agit bien d’une liste.

Le diagramme suivant permet de mieux comprendre comment naviguer dans le modèle UML:

three-tiers-uml-usage

Puis nous récupérons le DAO affecté à une entité:

helper context UML!Class def : dao : UML!Class =
	self.usages->collect(u|u.client)->flatten()->select(c|c.isDao)->first();

Pour chaque lien d’usage, on récupère (collect) les sources (client) et ne retenons (select) que les sources qui sont des DAO. Nous ne retenons que le premier de la liste (first). Les lecteurs attentifs auront remarqué le flatten. Celui-ci est nécessaire car l’attribut client est une liste. Nous récupérons donc une liste de liste qu’il nous faut applatir.

Nous avons maintenant tout ce qu’il nous faut pour écrire la transformation en elle-même. C’est une transformation de type refining. Ce mode spécial d’ATL permet de travailler par exception. Par défaut, tous les éléments du modèle d’entrée sont recopiés dans le modèle de sortie. Il nous suffit donc de définir uniquement les règles qui sont susceptibles de modifier des éléments, en l’occurrence les classes qui sont des entités et n’ont pas de DAO affecté:

-- @nsURI UML=http://www.eclipse.org/uml2/2.1.0/UML
-- Author: Cédric Vidal, http://blog.proxiad.com
module umlscaff;
create OUT : UML refining IN : UML;

-- Main rule

rule scaffoldDaoForEntity {
	from
		-- Matcher les Class UML qui sont des Entity et qu'ont pas de DAO associé
		entity : UML!Class (entity.isEntity and not entity.hasDao)
	to
		-- Recopier l'Entity telle quelle
		refined : UML!Class,

		-- Scaffolder le  DAO ...
		dao : UML!Class (
			name <- entity.defaultDaoName,
			package <- entity.package
		),
		-- ... et la relation d'Usage du DAO vers l'Entity
		usage : UML!Usage (
			name <- entity.defaultDaoUsageName,
			client <- dao,
			supplier <- entity
		)
}

Certaines conventions n’ont pas encore été introduites:

helper context UML!Class def : hasDao : Boolean = not self.dao->oclIsUndefined();
helper context UML!Class def : defaultDaoName : String = self.name + 'Dao';
helper context UML!Class def : defaultDaoUsageName : String = self.defaultDaoName + 'Usage';

Le helper hasDao permet de savoir si une class a un DAO associé. Il renvoie vrai si le helper dao renvoi une valeur définie. Les helpers defaultDaoName et defaultDaoUsageName sont utilisés pour construire les noms des éléments scaffoldés: DAO et Usage.

En écrivant cet exemple, je suis tombé sur ce qui semble être un bug de ATL 3.0.0. Si vous avez été attentif aux détails du code source, il se peut que vous ayez remarqué que le DAO scaffoldé est placé dans le même package que l’Entity ligne 19, cependant, nous ne plaçons pas la relation d’Usage où que ce soit. Concrètement, la relation d’Usage scaffoldée va donc se retrouvée à la racine du modèle.

En fait, j’ai bien essayé de placer cette relation dans le même package que l’Entity tout comme le DAO scaffoldé, mais sans succès. Il se peut qu’il s’agisse d’un bug. J’ai contacté l’équipe ATL et suis en attente d’un retour à ce sujet. Pour plus d’infos, voire Bug in ATL 3.0.0 final refining mode ?.

Dans cet exemple, pour rester simple, nous ne montrons pas le scaffolding des méthodes finder CRUD create, read, update et delete dans le DAO mais il serait bien sûr possible de les ajouter. Néanmoins, il serait plus approprié de les ajouter dans une transformation dédiée chaînée en sortie de la transformation ci-dessus afin de bénéficier du scaffolding des finders aussi bien pour les DAO scaffoldés que pour les DAO saisis à la main. Par contre, multiplier le nombre des transformations pose un problème évident de performance.

Les avantages de cette implémentation de scaffolding statique “aveugle” sont:

  1. Accélère la saisie du modèle
  2. Permet de minimiser la quantité d’éléments à modéliser
  3. Permet d’enrichir le modèle d’entrée tout en facilitant son usage pour l’utilisateur novice
  4. L’utilisateur peut toujours choisir de reprendre le contrôle en définissant lui-même les éléments scaffoldés

Les inconvénients:

  1. L’utilisateur ne peut référencer les éléments scaffoldés. C’est pénible pour l’utilisateur si le modèle prévoie de modéliser des Services qui référencent des DAOs, l’utilisateur sera alors contraint de modéliser explicitement tous les DAOs qu’il veut pouvoir injecter dans des Services. Suivant la nature du modèle d’entrée, cet inconvénient pourra s’avérer moins gênant.
  2. Le scaffolding étant ici statique, l’utilisateur ne peut voir en temps réel le résultat du scaffolding du modèle qu’il édite. Il ne peut donc pas savoir à priori quels éléments seraient scaffoldés s’il ne les modélisait pas. Il doit donc connaitre la “documentation” de l’environnement MDSD. Cette approche ne me satisfait pas dans la mesure ou je préfère les approches intuitives.

Le second inconvénient peut être aisément pallié en exécutant en live la transformation ATL lors de l’édition du modèle. ATL expose en effet une API permettant d’exécuter une transformation ATL de manière programmative. Le résultat du scaffolding pourrait alors être visualisé en temps réel dans une vue dédiée de l’IDE.

Pallier au premier inconvénient est plus délicat. Pour pouvoir référencer les éléments scaffoldés, il est nécessaire de les rendre visible dans le modèle saisi par l’utilisateur. L’idéal serait de pouvoir exécuter la transformation ATL “sur place”. Le mode inline d’ATL bien que prévu pour “modifier” le modèle d’entrée crée bien un nouveau modèle en sortie. Il n’est donc pas possible à priori de modifier le modèle édité par l’utilisateur en live. C’est par contre en théorie possible. L’idéal serait que ce mode soit directement supporté par ATL. Quoi qu’il en soit, une alternative consiste à calculer la différence entre le modèle d’entrée et le modèle scaffoldé de manière à appliquer la différence sur le modèle d’entrée. Un formidable composant de l’écosystème Eclipse Modeling permet précisément de faire cela: EMF Compare. Affaire à suivre donc.

Cet exemple a permis d’illustrer une implémentation possible de scaffolding statique et “aveugle”. Nous avons également émis quelques hypothèses permettant d’envisager à plus on moins long terme du scaffolding “sur place” dynamique avec ATL. Nous verrons dans le prochain article de cette série que c’est d’hors et déjà possible avec un autre type de moteur: un moteur d’inférence ! Nous expliquerons pourquoi un tel type de moteur est très intéressant pour répondre à ce problème.

Le code source complet de cet exemple est disponible en pièce jointe de cet article.

PS: Pour ceux qui voudraient plus d’informations sur ATL, ne ratez pas le colloque ATL qui aura lieu pendant les 10èmes Rencontres Mondiales du Logiciel Libre du 7 au 10 Juillet à Nantes.

Advertisements