Une première approche du Camel d’Apache

Raphaël Delaporte (@rafdelaporte) nous a proposé de dompter un chameau cette semaine au JUG Nantes.

camel.png

Et ne vous fiez pas à cette photo ! Notre chameau d'informaticien n'est pas un paresseux, bien au contraire ! Retour sur une présentation forte intéressante d'Apache Camel.

apache-camel-logo.png

Apache Camel s'appuie et reprend les Entreprise Integration Patterns - EIP (à ne pas confondre avec les Design Patterns qui sont utilisés pour la conception objet).

Les patterns EIP

Voici les principaux patterns du plus simple au plus complexe (extrait de la présentation).

eip-patterns-thumbnail.png

En les combinant, les possibilités sont quasi-infinies. Vous trouverez la liste des Patterns implémentés dans Camel sur le site officiel du framework.

Un livre complet sur le sujet existe "Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions". L'auteur n'est autre que le créateur de ce concept, autant dire que c'est la référence dans ce domaine.

Faire des EIP avec Apache Camel

Pour mettre en oeuvre les EIP, on trouve trois éléments de base dans Camel :

  • Route
  • Processor
  • Endpoint (dans la littérature française, le terme est parfois traduit par "point d'extrémité")

Une route est conceptualisée de la manière suivante : la liaison de deux endpoints par un processor. Ainsi, nous avons un endpoint de départ et un endpoint de fin, un endpoint est associé à une ressource. Une route symbolise un traitement plus ou moins complexe en fonction du traitement voulu (processor) et de l'EIP choisi.

Pour répondre au besoin, on peut imbriquer les routes, les chaîner, appliquer des traitements différents tout au long de la route, etc.

Dans Apache Camel, la définition de routes peut se faire de deux manières :

  • Soit en utilisant le langage Java (domain specific language - DSL). Apache Camel fournit une API. Très pratique pour l'autocomplétion dans l'IDE.
  • Soit en utilisant le langage XML. Plus verbeux, donc un peu moins lisible et finalement un peu moins pratique. Avec Camel, on préconise l'utilisation du DSL lorsque les routes sont complexes.

Dans la suite, nous privilégierons donc l'écriture en code Java. Dans tous les cas, un minimum de code XML est nécessaire pour la configuration de Camel (le contexte Camel, les sources de données, etc.).

Voici un exemple de route :

import org.apache.camel.builder.RouteBuilder;

public class MyFirstRoute extends RouteBuilder {
    @Override
    public void configure() thwrows Exception {
        // Ma première route
        from("file:/Users/fabian/src").to("file:/Users/fabian/dest").end();

        // D'autres routes peuvent être définies
        // ...
    }
}

Le mot clé .end() n'est pas obligatoire, mais il est conseillé pour bien délimiter les routes et permettre de s'y retrouver (maintenabilité). Dans cet exemple, le traitement est très simple et il n'y a qu'une seule route, il n'est pas vraiment indispensable.

La présentation était ponctuée de nombreuses démos. A partir d'un projet Maven bien configuré (dépendances Spring, Camel, contextes...), Raphaël nous a montré quelques cas d'utilisation écoles :

  • Déplacer les fichiers d'un répertoire à un autre (il s'agit du bout de code ci-dessus). Tous les fichiers créés dans le répertoire src seront déplacés dans le répertoire dest. Cette route s'écrit en une ligne, témoignant de la puissance d'un tel outil.
  • Créer des fichiers correspondants à chaque message poolé dans une queue JMS. Pour la présentation, Raphaël a utilisé Apache ActiveMQ, un message broker JMS. Ce cas illustre le pattern Splitter.
  • Transférer des messages JMS vers une queue en fonction de leurs contenus. Ce cas illustre le pattern Content-based Router.

La route est la suivante, en supposant que queue.in existe dans ActiveMQ et qu'on y poole des messages :

import org.apache.camel.builder.RouteBuilder;

public class MyContentBasedRoute extends RouteBuilder {
    @Override
    public void configure() thwrows Exception {
        from("amq:queue.in")
            .choice()
                .when(header("JMSCorrelationID").isEqualTo("nantes"))
                    .to("amq:queue.nantes")
                .when(header("JMSCorrelationID").isEqualTo("rennes"))
                    .to("amq:queue.rennes")
                .otherwise()
                    .to("amq:queue.others")
        .end();
    }
}

Depuis la console d'admin d'ActiveMQ, on crée quelques messages de test avec les différents cas possibles. En fonction de son header, le message est routé vers la bonne queue.

Il est possible de chaîner des prédicats avec les opérateurs logiques habituels afin de créer des critères de routage plus complexe. Ici, le prédicat est simple et se base seulement sur le header du message. On aurait pu également se baser sur le body pour faire un routage plus fin des messages.

  • Créer des messages JMS à partir du contenu d'un fichier XML. Ce cas illustre le pattern Dynamic Router.

Voici le contenu du fichier messages_jug.xml :

<jug>
    <nantes>Hello from Nantes</nantes>
    <paris>Hello from Paris</paris>
    <rennes>Hello from Rennes</nantes>
    <nantes>Hello again from Nantes</nantes>
    <paris>Hello again from Paris</paris>
    [...]
</jug>

Avec Camel, on veut extraire les différents messages pour les envoyer dans une queue.

Voici le code de la route correspondante :

import org.apache.camel.builder.RouteBuilder;

public class MyDynamicRoute extends RouteBuilder {
    @Override
    public void configure() thwrows Exception {
        from("files:/Users/fabian/src/messages_jug.xml")
            .split(xpath("/jug/child::*"))
        .to("amq:queue.jugs");
    }
}

On aurait pu router vers un autre fichier en filtrant sur les messages de Nantes. Les possibilités sont multiples.

Optimisations Camel

On peut se poser le problème de l'indisponibilité... Que se passe-t'il dans le cas du traitement d'un message si la machine crashe et n'a pas le temps de router le message ? Malheureusement, vous perdez le message ! Celui-ci a été consommé dans la queue de départ, mais n'a jamais pu atteindre sa destination. La solution est de passer la route en mode transactionnel. Cela se fait en ajoutant .transacted() juste après le .from(...) et en prenant soin d'indiquer à votre source JMS de se mettre en mode transactionnel (configuration XML).

On peut aussi noter que, par défaut, le traitement est monothread. Une amélioration est d'activer la parallélisation sur le processor (ici split) en ajoutant l'option .parallelProcessing() juste après le .split(...).

Nous avons parlé de file et de jms, mais les composants disponibles dans Camel sont légion grâce à une communauté très active. Cette page recense l'ensemble des composants disponibles. Citons jdbc, ftp, gmail, atom, pop, imap entres autres.

Faire des EIP avec Spring Integration

Raphaël nous a parlé de Spring Integration, une alternative à Camel. Spring Integration ne laisse pas le choix à l'utilisateur et s'utilise avec la notation XML exclusivement.

D'après Raphaël, il s'intègre mieux avec Spring Batch, mais l'objectif n'était pas de juger les deux outils. Chacun a ses avantages et ses inconvénients.

Voici l'exemple du transfert des fichiers d'un répertoire à un autre, cette fois écrit avec Spring Integration :

<channel id="myFirstChannel" />

<file:inbound-channel-adapter id="filesIn"
        channel="myFirstChannel"
        directory="file:/Users/fabian/src" />
       
<file:outbound-channel-adapter id="filesOut"
        channel="myFirstChannel"
        directory="files:/Users/fabian/dest" />

La notion de route n'est pas présente, on parle de channel. Une channel relie deux composants. On se rapproche clairement de la philosophie de Spring qui place la notion de composant au centre. Des composants que l'on injecte et que l'on relie entre eux (et c'est logique puisqu'il s'agit du même éditeur SpringSource).

En fait, le terme route n'est utilisé que par Camel. Spring Integration est plus fidèle aux concepts des EIP en utilisant des termes similaires comme channel.

Autre différence, la channel apparait concrètement dans Spring Integration alors que la route dans Camel est implicite (pas de mot-clé route()). Si on reprend notre première route :

from("file:/Users/fabian/src").to("file:/Users/fabian/dest");

La route est symbolisée par le "." entre les deux endpoints. Il n'est pas possible de la configurer. A ce niveau, Spring Integration est un peu plus poussé avec la possibilité de configurer le type de channel notamment.

Filmée lors d'un BreizhJUG, cette présentation est disponible sur Parleys.

2 commentaires

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *