15 mars 2008

YAML est au-dessus de XML

Développeur Java.

Des montagnes de fichiers XML pour la configuration, pour les tests, pour la sérialisation de données, parfois même pour les logs.

Une envie de penser plus vite.

Solution : adoption radicale de YAML.

Un exemple

Pour mes tests je joue beaucoup avec des descriptions de données au format XML. Ainsi, je peux manipuler des valeurs de référence (pour les tests) sous la forme suivante :

currentUser.xml :
<DataBean name="currentUser"
class="com.avcompris.model.User" scope="request">
<properties>
<property name="firstName"
class="java.lang.String">Dominique</property>
<property name="lastName"
class="java.lang.String">Vandrault</property>
<property name="birthDate"
class="org.joda.time.DateTime">1978-01-06</property>
</properties>
</DataBean>

Première discussion. On m'objectera qu'il vaudrait mieux avoir deux fichiers XML, un pour la description de la structure et un pour les valeurs. Cela donnerait éventuellement :

User.xml :
<DataBeanClass
class="com.avcompris.model.User" alias="User">
<properties>
<property name="firstName"
class="java.lang.String"/>
<property name="lastName"
class="java.lang.String"/>
<property name="birthDate"
class="org.joda.time.DateTime"/>
</properties>
</DataBeanClass>

currentUser.xml :
<User name="currentUser" scope="request">
<firstName>Dominique</firstName>
<lastName>Vandrault</lastName>
<birthDate>1978-01-06</birthDate>
</User>

C'est un premier choix à faire. L'idée n'étant pas de faire de l'architecture mais bien de pisser du test, je n'ai aucune envie de me retrouver avec des fichiers de description dans tous les sens, qui deviendront impossibles à maintenir au bout de 30 refactorings. Pourquoi pas écrire des XML Schemas dès maintenant, aussi ?

Pour mes procédures de tests je conserve donc le principe du fichier unique, avec méta-données côte-à-côte avec les données.

Mon fichier de départ est bien le suivant :

currentUser1.xml :
<DataBean name="currentUser"
class="com.avcompris.model.User" scope="request">
<properties>
<property name="firstName"
class="java.lang.String">Dominique</property>
<property name="lastName"
class="java.lang.String">Vandrault</property>
<property name="birthDate"
class="org.joda.time.DateTime">1978-01-06</property>
</properties>
</DataBean>

Première idée, accepter les valeurs par défaut. Convention over configuration. Ainsi, je pourrais décider par convention que quand l'attribut @class est absent, la propriété est forcément de type java.lang.String. Et puis supprimons le tag <properties> qui ne sert à rien (il serait très utile si je souhaitais écrire un XML Schema validant mon XML, mais... ce n'est pas le cas pour le moment). Voyons ce que ça donne :

currentUser2.xml :
<DataBean name="currentUser"
class="com.avcompris.model.User" scope="request">
<property name="firstName">Dominique</property>
<property name="lastName">Vandrault</property>
<property name="birthDate"
class="org.joda.time.DateTime">1978-01-06</property>
</DataBean>

Ah, mieux. Beaucoup mieux !

Deuxième idée, passer à des éléments qui ont le nom de la propriété. On élimine donc, par convention, l'attribut @name (et parallèlement, on s'éloigne de plus en plus de la possibilité d'écrire un XML Schema validant non trivial).

currentUser3.xml :
<currentUser
class="com.avcompris.model.User" scope="request">
<firstName>Dominique</firstName>
<lastName>Vandrault</lastName>
<birthDate
class="org.joda.time.DateTime">1978-01-06</birthDate>
</currentUser>

Ça avance. Je ne me pose pas la question des sous-DataBeans, car je sens que ça me ferait reculer dans ma joie simplificatrice.

Continuons. Passons maintenant en tant qu'attributs de leur parent les éléments uniques qui n'ont pas d'attributs.

currentUser4.xml :
<currentUser
class="com.avcompris.model.User"
scope="request"
firstName="Dominique"
lastName="Vandrault">
<birthDate
class="org.joda.time.DateTime">1978-01-06</birthDate>
</currentUser>

OK. C'est un peu mélangé, ce n'est pas uniforme, mais j'y gagne.

Chouette.

...

Allez, j'arrête le cinéma. Ce XML est immonde, il n'est pas évolutif, il n'est pas validable, et il est moche. Moche comme du... XML.

Dites-moi que vous n'y avez pas cru...

L'approche YAML

YAML est un format de données qui est abondamment utilisé dans le monde de Ruby on Rails.

Quand je choisis YAML pour décrire mes données, je ne me dis pas « comment être sûr que je pourrai écrire un XML Schema validant plus tard », mais « comment ai-je envie d'écrire mes données ».

Le parsing, les transformations et la validation viendront bien plus tard. En fait, le plus souvent en passant par une étape XML intermédiaire.

En premier lieu je me contente d'une utilisation ad hoc du format.

Ainsi, mon fichier currentUser4.xml deviendrait :

currentUser4.yaml :
currentUser:
class: com.avcompris.model.User
scope: request
value:
firstName: Dominique
lastName: Vandrault
birthDate:
class: org.joda.time.DateTime
value: 1978-01-06

Beaucoup mieux que l'équivalent XML.

Mais on peut aller plus loin :

currentUser5.yaml :
currentUser:
com.avcompris.model.User:
firstName: Dominique
lastName: Vandrault
birthDate:
org.joda.time.DateTime: 1978-01-06

(En passant, je supprime l'attribut @scope, en décidant qu'il vaut "request" par défaut. J'aurais aussi pu le faire dans le cas du XML).

Voici les « équivalents » YAML des autres fichiers XML :

currentUser3.yaml :
currentUser:
class: com.avcompris.model.User
scope: request
value:
firstName: Dominique
lastName: Vandrault
birthDate:
class: org.joda.time.DateTime
value: 1978-01-06

currentUser2.yaml :
- DataBean:
name: currentUser
class: com.avcompris.model.User
scope: request
properties:
- name: firstName
value: Dominique
- name: lastName
value: Vandrault
- name: birthDate
class: org.joda.time.DateTime
value: 1978-01-06

currentUser1.yaml :
- DataBean:
name: currentUser
class: com.avcompris.model.User
scope: request
properties:
- name: firstName
class: java.lang.String
value: Dominique
- name: lastName
class: java.lang.String
value: Vandrault
- name: birthDate
class: org.joda.time.DateTime
value: 1978-01-06

Ou le « degré zéro » de YAML :

currentUser0.yaml :
- DataBean:
name: currentUser
class: com.avcompris.model.User
scope: request
properties:
- property:
name: firstName
class: java.lang.String
value: Dominique
- property:
name: lastName
class: java.lang.String
value: Vandrault
- property:
name: birthDate
class: org.joda.time.DateTime
value: 1978-01-06

« currentUser5.yaml », propre, intelligent, lisible, fait 6 lignes.

Le dernier fichier, « currentUser0.yaml », d'une structure digne d'un fichier XML validable, pour ne pas dire d'une base de données relationnelles, en fait 17. Quasiment 3 fois plus.

YAML :
  • ne demande pas d'indiquer les fins d'éléments (ouf !), car il se base sur l'indentation
  • n'a pas de notion de sous-élément ou d'attribut : on se débrouille avec l'arborescence. Disons qu'il y a bien la notion de maps et de lists, qui correspondraient à peu près aux attributs (noms uniques) et aux sous-éléments (noms éventuellement répétés), mais les maps peuvent avoir des sous-arborescences, contrairement aux attributs du XML.
  • est... lisible.
    • il n'y a pas de guillemets dans tous les sens
    • l'indentation fait partie de la syntaxe
YAML n'a rien à voir avec JSON. Je dirais grosso modo que YAML est orienté lisibilité des fichiers par des humains, tandis que JSON est orienté transfert de données entre machines. Mais surtout, JSON est fortement similaire à XML, tandis que YAML a une autre logique.

La place de YAML dans le processus de développement

J'utilise YAML pour les données de tests, la configuration, et les fichiers sources de génération.

Comme les composants côté serveur ou côté plugins savent surtout ingérer du XML, j'opère toujours une première phase de sérialisation de YAML vers un XML « neutre » équivalent.



La bibliothèque Java que j'utilise pour le parsing de YAML est JvYAML.

Voir aussi la bibliothèque JYaml.

La page de Wikipedia sur YAML : YAML.

3 commentaires:

Fred TU @SQLI a dit…

Belle petite démonstration de la puissance de la simplification. On remarque que cela se rapproche bcp des fichiers properties conventionnels lui rajoutant tout l'intérêt de XML, l'arborescence!

Cela doit bien fonctionner pour des petits fichiers de configuration, cependant, on peut se questionner de la lisibilité avec les retours à la ligne des éditeurs de textes et son utilisation sur de gros fichiers.

Qu'en penses tu?

David Andriana a dit…

Merci Frédéric pour le commentaire. En effet, on a la simplicité et la lisibilité des fichiers de propriétés, plus l'arbo du XML. YAML permet également de faire des références entre éléments ("&"), mais pour l'instant je ne l'utilise pas.

En restant bien dans la priorité d'avoir des données lisibles par un humain, sur des gros fichiers, je trouve au contraire qu'on y gagne par rapport à XML, pour lequel il faut obligatoirement dans ce cas un outil de visualisation.

Quant aux retours à la ligne, je n'ai pas encore eu ce problème alors que je formate mon code à 80 caractères : pour les longues chaînes de caractères, YAML permet de faire du multi-lignes (avec les marqueurs "|" ou ">"), et je n'ai pas eu d'arborescences trop profondes qui m'auraient obligé à couper mes lignes.

Cependant je pense que les vrais retours d'expérience sont à aller chercher du côté de RoR.

y a dit…

Je ne vois pas de démonstration. Tu dis que c'est mieux que du xml, sans argumenter vraiment. Pourquoi ? Parce qu'on doit fermer ses balises en xml ? Et pourquoi ne pourrait-on pas écrire un schéma pour ton exemple xml ? Tu dis que le xml ne valide pas. Tu valides ton yaml ?

Je pense que qu'il te manque, c'est surtout un éditeur, pas un nouveau format. Bonne nouvelle : il y en a plein :-)