Si vous avez déjà eu besoin de faire communiquer différents applicatifs entre eux (genre vous bossez dans l'informatique), vous allez adorer Apache Camel!
Je voulais écrire un court billet sur les tests dans Camel. Puis je me suis rendu compte que je n’avais jamais évoqué Camel sur ce blog. Ce qui est un peu étrange vu que c’est le framework que j’utilise tous les jours, sur plusieurs projets, depuis plus d’un an. Et que c’est l’un des outils les plus cools et les plus indispensables pour garder votre intégrité mentale dès qu’il s’agit de router des messages entre différentes machines/protocoles/wtf has imagined the architect guy before you.
Donc je vais vous raconter comment on peut faire des trucs géniaux dans Camel. D’abord le nom complet est Apache Camel, un projet qui fête ses 5 ans, avec pas mal de contributeurs, dont une grande partie vient de la société FuseSource (récemment acquise par RedHat). Pas vraiment de soucis à se faire sur la maturité du projet donc. Mais à quoi ça sert ? Il faut voir Camel comme un routeur de messages entre vos différents applicatifs, certains appelleront ça un ESB léger. D’ailleurs Apache Service Mix, qui est l’artellerie lourde en matière d’ESB Apache se base sur Camel pour sa gestion des routes. D’autres appelleront ça un framework d’intégration, à la Spring Integration mais en plus complet et plus agréable à utiliser. D’ailleurs si vous êtes un fan de Spring, aucun problème l’interopérabilité est vraiment excellente.
Les introductions à Camel me laissent toujours un peu sur ma faim, sans doute parce que ce n’est pas au premier coup d’oeil que l’on comprend sa puissance. Mais au fil des projets, on tombe sur souvent sur des use cases qui correspondent exactement à ce que propose Camel dans un de ses nombreux modules ou patterns, et c’est la que la magie opère. Ce n’est pas rare que l’on me demande d’ajouter une fonctionnalité et que Camel permette de répondre : ‘Pas de problème, il y a un pattern qui fait exactement ça’.
Il faut un peu de temps pour se faire au fonctionnement et appréhender les différents patterns. Car, au début, Apache Camel à été créé comme une implémentation Open Source des EIPs (Enterprise Integration Patterns), qui sont à peu près aussi chiants à lire que les Design Pattern : tant que l’on n’en a pas eu besoin ça n’a pas vraiment de sens.
Organisation
Quand vous démarrez un projet, vous n’avez besoin que d’une seule dépendance : camel-core (actuellement en version 2.10.x). Elle contient les fameux EIPs de base ainsi que la gestion des protocoles les plus courants (fichier par exemple). Ensuite si vous voulez faire quelque chose d’autre comme des appels ftp, il faut ajouter le module correspondant, camel-ftp, dans vos dépendances. La liste des modules disponibles est impressionante : vous pouvez interagir avec tout ce dont vous pouvez avoir besoin : de MongoDb à un AS400 en passant par un LDAP, Nagios, JDBC, des flux RSS… Il y a également des modules qui ne sont pas dans le repo officiel (c’était le cas des Websockets jusqu’à peu encore) car ils sont en cours de stabilisation, ou d’autres pour des problèmes de licence (module SAP par exemple). Tous les protocoles réseaux sont également gérés. Bref il faut faire quelque chose de très très exotique pour ne pas trouver son bonheur.
Pour avoir modestement contribué à quelques bugfixes et features, le code du core est très bien pensé, écrit et testé. Les modules sont eux de qualité un peu plus variable, mais tous ceux couramment utilisés sont très bien faits et production-ready.
A quoi ca ressemble une route ?
Ces routes peuvent être écrites en XML, mais comme 95% des développeurs je trouve ça pas terriblement fun, j’utilise l’autre alternative, la DSL fournie par Camel, qui permet d’écrire les routes directement en Java (qui a en plus l’immense avantage de vous donner l’autocomplétion dans votre IDE). C’est un peu perturbant au début, parce que l’on écrit pas des méthodes comme dans un projet classique, on écrit la déclaration des routes que les messages vont suivre dans l’application.
Par exemple :
from("file://inbox?delete=true").to("bean:handleOrder").to("file:outbox");
Voilà, ça c’est une route. Basique certes, mais qui permet de récupérer un fichier dans un répertoire inbox, d’envoyer son contenu (que l’on imagine être une liste de commande) à un bean Java qui s’occupera de les traiter, avant de placer le fichier de commandes traitées dans un répertoire outbox.
Même si on a jamais fait de Camel de sa vie, la DSL se lit assez bien, une déclaration de route se fait de la façon suivante :
- un point d’entrée (identifié par le ‘from’) qui s’appelle un Endpoint en langage Camel. Le triplet de déclaration d’un endpoint est souvent le même à savoir un protocole (ici ‘file’), un nom (ici ‘inbox’ qui correspond au répertoire) puis des options après le ? (ici pour indiquer que les fichiers sont supprimés après lecture)
- un ensemble d’étape (identifiés par ‘to’), la dernière étant le point de sortie, sur le même modèle.
Dans cette route il n’y a pas de pattern particulier, c’est une route basique du type ‘mon message va de là à là’. Camel va se charger pour vous de faire les conversions de type nécessaire. Si les commandes sont en xml dans le fichier, il va convertir le xml en objets Java pour pouvoir appeler le bean de traitement de commande, puis reconvertir en xml pour les placer dans le fichier de sortie.
Admettons que maintenant votre système de commande expose également un webservice REST qui doit appeler le même bean et également pousser le fichier de commandes traitées dans le répertoire outbox. Easy! D’abord on découpe la route précédente en 2, grâce au mot clé ‘direct’ qui est en fait un moyen de créer des sous routes.
from("file://inbox?delete=true").to("direct:orders"); from("direct:orders").to("bean:handleOrder").to("file:outbox");
Maintenant ajouter le webservice se résume à créer une route qui écoute les requêtes entrantes grâce au module cxfrs pour du REST et qui appelle la route direct:orders
from("cxfrs:bean:orderRest").to("direct:orders");
Il y a bien sûr un peu de code pour indiquer à cxfrs que l’on attend les requêtes sur tel port avec tels paramètres, mais la route en elle-même se réduit à ça.
Pattern
Avant de voir un premier pattern, il faut comprendre comment sont organisés les messages dans Camel. Nous avons vu que la déclaration de route est une suite d’étapes. Entre chacune de ces étapes, l’objet qui va transiter est un Exchange, qui contient un message In et un message Out. A chaque étape l’exchange entre avec le message In rempli et le message Out vide, puis la partie Out va être remplie avec le résultat de l’étape. Lors de l’étape suivante, ce message Out va devenir le message In et ainsi de suite. Une fois que l’on a compris ce principe de chaine (un out devient le in de l’étape suivante, qui remplit le out, qui devient à son tour le message in de l’étape suivante etc…), tout devient très simple. Un message en lui même est composé d’un ensemble de header et d’un body.
Donc si vous voulez utiliser un pattern Camel comme par exemple filtrer les messages, vous utilisez la fonction filter dans la DSL
from("direct:orders").filter(header("filiale").isEqualTo("Europe")).to("direct:europe");
Si vos commandes de la filiale Europe doivent subir un traitement spécial, vous pouvez filtrer les messages sur ce header. Camel fournit pas mal de méthode pour établir ces filtres de façon simple (ici sur un header), mais vous pouvez également appeler une méthode de votre métier pour filtrer.
Allez, un dernier exemple de pattern moins connu que filter et qui sert beaucoup : ‘recipientList’. Il permet de router dynamiquement un message.
Par exemple :
from("direct:orders").recipientList(header("filiale").prepend("direct:")); from("direct:europe").to(...); from("direct:asie").to(...);
Camel va appeler la sous-route qui correspond à la bonne filiale. Si vous ajouter une nouvelle filiale dans votre SI, vous avez seulement à écrire la sous route qui correspond. C’est pas cool ça?
J’ai encore des dizaines de trucs géniaux sur Camel, mais on va s’arrêter là pour aujourd’hui et on reprendra la prochaine fois. Stay tuned!