BaseX, ou le FLWOR power en open-source

BaseX (basex.org) est un SGBD natif XML open-source, un outil particulièrement adapté à la manipulation et à la transformation de données complexes. Il me sert actuellement en particulier à migrer les données d’un ensemble de services VOD OTT (Universcine.fr, Universcine.be, Volta.ie, LeKino.ch, Cinemasalademande.com) et ses fonctionnalités m’étonnent tous les jours. J’espère que ce qui suit vous mettra l’eau à la bouche.

La complexité d’un catalogue VOD

Un catalogue VOD est constitué de films, d’artistes, de sociétés, d’évènements, d’articles. Les utilisateurs des services VOD louent ou achètent les films, émettent des avis, créent des playlists, se suivent les uns les autres. Toutes ces entités ont de nombreuses propriétés et relations, ce qui aboutit à un modèle conceptuel complexe. UniversCiné gère ainsi plus de 4.000 films, 40.000 artistes, des milliers de sociétés, éditions de festivals ou utilisateurs. Migrer ces données d’un modèle datant de 2006 à un modèle plus complet n’est pas évident, d’autant que certaines données doivent être nettoyées ou enrichies au passage : par exemple, les mots-clés libres sont mappés sur des taxonomies de genres et thèmes, des collections de films, les films acquièrent des identifiants standardisés (ISAN), les données HTML des articles sont nettoyées, des recommandations de films sont ajoutées dans les structures finales.

Migrer, exporter

Outre une migration aisée des données de catalogues VOD, nous avons besoin d’exporter ces données vers Free, qui développe un moteur de recherche VOD cross-catalogues sur la Freebox, ainsi que vers le CNC (Centre National du Cinéma), qui développe actuellement un service d’aide au référencement de l’offre VOD légale. Free demande un export XML classique, mais le CNC demande la mise au point d’un export JSON par Web Service REST.

De telles transformations de données sont généralement réalisées avec un outil d’ETL (Extract-Transform-Load) type Talend, mais la complexité des modèles, leur évolutivité, leur implémentation (la source était gérée par le framework Python Django, la destination par le framework PHP Symfony 2 et par une base de données documentaire Jackrabbit), les étapes de nettoyage de contenu, tous ces besoins aboutissent à des projets demandant une très grande expertise sur l’outil ETL choisi, et ont un coût final élevé.

Côté BaseX, notre effort se résout à la mise en place de l’applicatif BaseX sur une VM, à une expertise du langage de requête XQuery et à un script Python pour animer les transformations.

BaseX

Une base de données XML s’avère parfaite pour la migration ou l’export de données complexes. Elle permet d’ingérer des dumps XML provenant d’un SGBD relationnel source, de modifier au vol la structure de ces données, intégrer des données externes, exporter le résultat sous la forme d’archives XML / JSON ou bien d’exposer le contenu via une API REST.

La version 7.9 de BaseX s’installe sous Windows en moins de 10 secondes. Sous Linux, l’installation et la configuration de l’applicatif est un peu plus complexe, surtout si l’on désire utiliser les possibilités d’accès à la base via une interface REST (il faut mettre en place le WAR dans ce cas).

BaseX repose sur une architecture client-serveur. Le produit dispose d’un client en ligne de commande, d’une API en différents langages et d’une interface homme-machine bien pratique.

Le GUI BaseX

Je lance le GUI BaseX, et crée une base locale à partir d’un dump XML (Database -> New -> Browse -> film-dump.xml ; name : ucfr ; format : XML). Un fichier de 17 Mo est chargé et indexé en moins de 4 sc. Je fais de même pour le dump des artistes, des sociétés … qui composent la base de données source d’UniversCiné.

La base de données locale est dès à présent accessible à des requêtes XQuery ou des recherches plein-texte.

Capture baseX

Illustration : Recherche dans la collection ucfr de tous les films produits en 2013

Une remarque en passant : si l’on installe BaseX sur un serveur distant, seule la connexion et la création d’utilisateurs sont possibles. Il n’est pas possible de requêter un serveur distant avec cet outil [1] et seul le client en ligne de commande peut interagir avec un serveur distant.

XQuery

Je ne vais pas faire ici un cours sur XQuery, disons simplement qu’il s’agit du langage de requêtes de bases de données du monde XML, standardisé par le W3C dès 2007, adopté par l’ensemble des SGBD natifs XML mais aussi par les SGBDR majeurs (IBM DB2 PureXML, SQLServer 2005, Oracle 11g R1).

Une requête XQuery s’applique sur un document XML ou sur une collection de documents (la structure qui est au cœur de toute base de données XML). Toute expression XPath 2.0 est une forme simple de requête XQuery, mais XQuery constitue en fait un langage de programmation complet, qui permet de créer des requêtes complexes et des fonctions réutilisables.

Une requête XQuery évoluée s’écrit sous une forme nommée FLWOR (prononcer « flower », d’où l’illustration de l’article ;-)), qui s’inspire beaucoup du SELECT FROM WHERE ORDER BY de SQL.

Une requête XQuery permet de réaliser des filtres sur des données indexées, des jointures inter-documents et des transformations de structure : c’est ce qui en fait un outil adapté à des manipulations de type ETL.

XQuery a bénéficié lors de sa conception de fées du monde SQL, documentaire et XML. Mature dès sa première version, XQuery a néanmoins évolué pour atteindre en 2014 une version 3.0 déjà parfaitement intégrée à BaseX.

Le client en ligne de commande et l’API

Les commandes du client en ligne de commande sont pratiques [2], en voici quelques exemples :

  • show users
  • list
  • create db ucfr
  • open ucfr
  • add ftp://user:password@ftp.server.com:21/ucfr/film-dump.xml
  • optimize
  • info db
  • xquery /films/film[productionYear=”2014”]
  • close
  • exit

Une API dotée de commandes identiques est disponible dans la plupart des langages de programmation, dont Java, C#, Python, PHP ou javascript (node.js).

BaseX REST API

BaseX dispose d’un serveur REST qui permet de lancer des requêtes XQuery passées en paramètre.

Ex : http://localhost:8984/rest/ucfr?query=films/film[1]

Dans cet exemple, la requête s’applique au contenu de la collection (i.e. base de données) « ucfr ».

La structure retournée doit avoir un élément racine. Si la requête ne retourne pas d’élément racine, on peut en ajouter un via le paramètre wrap=yes

http://localhost:8984/rest/ucfr?query=films/film[productionYear=%222014%22]&wrap=yes

le résultat est alors encadré d’une balise

<rest:results xmlns:rest="http://basex.org/rest"><rest:result> … </rest:result></rest:results>

Les requêtes XQuery complexes peuvent être stockées sur le serveur, dans le répertoire [home]/webapp (Windows) ou [home]/BaseXWeb (Linux), dans des fichiers d’extension .xq. Elles sont alors appelées via le nom du fichier :

Ex : http://localhost:8984/rest?run=somefilms.xq

A noter : la requête stockée doit identifier chaque collection (i.e. base de données) sur laquelle s’applique une expression XPath par une instruction de type collection(« <database> »)/<xpath>

BaseX RESTXQ API

L’interface REST introduit ci-dessus est très pratique, mais ne permet pas de « dessiner » les URLs d’accès aux requêtes. Une interface alternative, nommée RESTXWQ, ajoute cette fonctionnalité élégante.

Les modules XQuery, d’extension .xqm, sont déposés dans le répertoire [home]/webapp. Les fonctions sont décorées de propriétés qui précises quel verbe http donne accès à la fonction, quels paramètres de la fonction sont véhiculés dans l’URL, la query string ou les headers http.

Voici un exemple de fonction ainsi décorée, appelée en local via http://localhost:8984/api/companies  :

module namespace api = 'http://universcine.com/v3/api';
declare
%rest:path("/api/companies")
%rest:query-param("offset", "{$offset}", 0)
%rest:query-param("limit", "{$limit}", 10)
%rest:query-param("locale", "{$locale}", "fr")
%rest:GET
function api:companies(
$limit as xs:integer, $offset as xs:integer, $locale as xs:string)
as element(companies)
{
<companies>
{
for $c in collection("cinedb")/company[position() gt $offset and position() le $offset+$limit]
return
$c
}
</companies>
};

Sérialisation JSON

Quand on parle d’interface REST, on parle également souvent de sérialisation JSON. Je ne vais pas m’étendre ici sur les avantages et inconvénients de JSON par rapport à XML … les deux formats sont complémentaires, c’est tout. Les concepteurs de BaseX ont pris ce besoin en compte, en permettant en particulier de transformer en JSON les structures XML issues d’une requête XQuery.

Cependant, les deux modèles présentent des différences fondamentales : XML est adapté aux données semi-structurées tandis que JSON est adapté aux données orientées objet. Pour réaliser la structure JSON de nos rêves, il faut travailler un peu.

En résumé, chaque élément XML destiné à devenir un objet JSON doit être doté d’un attribut « type=’object’ », chaque élément XML destiné à devenir un array JSON doit être doté d’un attribut « type=’array’ », et chaque futur élément d’un array JSON doit être nommé « _ ».

Voici un petit exemple de sculpture XML nécessaire pour l’obtention d’une structure JSON sympathique :

<images type="array">
{ for $i in $f/images/image
return
<_ type="object">
{ element uuid {data($i/@uuid)},
element src {data($i/@src)},
$i/category, $i/title
}
</_>
}
</images>,

Qui donnera :

"images": [
{
"uuid": "ucdb:image:82122",
"src": "http://media.universcine.com/3c/49/3c49f4ba-3384-11e4-944a-954866b2bff7.jpg",
"category": "poster",
"title": "aya-de-yopougon-affiche.jpg"
},

Le document XML doit être encadré par un élément <json type= »object »> ou <json type= »array »>, et la transformation JSON peut être provoquée dans la requête XQuery elle-même à l’aide de la fonction json :serialize() encadrant la structure XML destinée à transformation JSON.

Conclusion

Grâce à la mise en place d’un serveur BaseX, par transformation XML de données exportées des bases de données relationnelles de mon client, intégration de données complémentaires (les nouvelles taxonomies de genres et thèmes par exemple), transformation XQuery et nouvelle indexation des données résultantes, création d’une API REST JSON et de plusieurs exports XML, je peux exposer avec efficacité les données des différents services VOD d’UniversCiné, un travail réalisé en quelques jours seulement.

Cela me donne envie de poursuivre l’utilisation de BaseX pour d’autres cas d’utilisation, malgré quelques limitations que je vais citer rapidement :

  1. L’indexation incrémentale des données n’est pas toujours efficace : c’est un problème oublié des utilisateurs de SGDBR, mais un cas déjà rencontré avec des moteurs de recherche plein-texte et leurs indexes inversés. Par défaut, les indexes sont désactivés dès qu’un document est ajouté ou mis à jour dans une collection. Une commande d’optimisation recrée ces indexes, mais cette procédure peut prendre quelques secondes sur une collection importante. Une option (UPDINDEX, et AUTOOPTIMZE dans la version 8.0) active l’indexation incrémentale, mais augmente le temps de mise à jour de la collection, ce qui limite son intérêt aux collections de petite et moyenne taille.
  2. La mémoire utilisée pour les transformations n’est pas toujours facile à gérer. Par défaut, la JVM utilisée par BaseX est limitée à 512 Mo. J’ai été amené à saturer cette mémoire lors de transformations complexes, ce qui m’a amené à devoir tronçonner certains processus de transformation.

 

1 https://mailman.uni-konstanz.de/pipermail/basex-talk/2013-December/005863.html

2 http://docs.basex.org/wiki/Standalone_Mode

1 thought on “BaseX, ou le FLWOR power en open-source

  1. Merci Laurent pour ce très chouette article qui m’ouvre des perspectives. C’est toujours didactique, clair et bien écrit : un plaisir.

Comments are closed.

Close