Blog Zenika

#CodeTheWorld

Java

Introduction au framework Camel – Partie 2

Dans un précédent article nous avions introduit le framework de routage Camel. A présent, entrons un peu plus dans le vif du sujet avec l’étude de quelques types de endpoints. Dans ce second article nous allons voir plus en détail l’utilisation des endpoints en nous focalisant plus spécifiquement sur certains d’entre eux.

Comme vu précédemment, Camel a vocation à être utilisé pour résoudre des problématiques d’intégration de systèmes. Il peut pour cela être utilisé au sein d’un ESB ou d’un serveur d’applications, mais il peut également être utilisé en mode standalone dans un programme Java comme vu dans l’article précédent.
Par ailleurs, dans les exemples toutes les routes sont définies en utilisant le DSL Java car il s’agit de la méthode la plus flexible et la moins verbeuse mais je rappelle qu’il est toujours possible de décrire une route en XML.

Les Endpoints

Nous avions vu ce que sont les endpoints dans l’article précédent. Nous allons maintenant voir plus précisément comment les utiliser.
Endpoints et Composants
La notion de composant dans Camel est un concept clé qu’il est important de bien appréhender. Le terme peut être confusant car ce que l’on nomme composant est en réalité une fabrique de endpoints. Un composant implémente l’interface Component. Elle définie principalement la méthode createEndpoint(String uri) qui renvoie un nouvel objet de type Endpoint.
Utilisation des URI
On peut tout à fait faire référence à des objets de type Endpoint dans la définition des routes mais souvent on préférera utiliser des URI. Dans l’article précédent nous avions déjà mis cela en oeuvre en définissant la route :

from("file://C:/factures/in").to("file://C:/factures/out");

Cette route qui relie deux endpoints de type file va déplacer tous les fichiers arrivant dans le répertoire in vers le répertoire out.
Lors de l’appel à la méthode from, Camel va instancier un endpoint file si c’est la première fois qu’il apparaît dans la définition des routes, sinon la référence vers le endpoint existant sera utilisée. Plus simplement, deux URI identiques impliquent un unique endpoint.
L’utilisation des options d’URI permet de configurer le endpoint. Par exemple, pour le consommateur de fichiers ci-dessus je peux définir le délais après lequel il va consommer pour la première fois et l’intervalle de temps entre deux consommations.

from("file://C:/factures/in?consumer.initialDelay=2000&consumer.delay=5000").to("file://C:/factures/out");

La page de documentation du composant file donne l’ensemble des options possibles pour ce type de endpoint.
Camel fourni environ 70 types de endpoint. De plus, en cas de besoins spécifiques il est toujours possible de créer ses propres endpoints.

Les endpoints « direct » et « seda »

Contrairement au endpoint file, direct et seda ne représentent pas une ressource physique. Il s’agit de endpoints logiques. ils sont utilisés pour communiquer à l’intérieur du contexte Camel.
direct :
Gardons l’exemple de transfert de fichiers du répertoire in vers le répertoire out et ré-écrivons cette route en deux morceaux de route reliés entre elles par un endpoint de type direct.

from("file://C:/factures/in").to("direct:filemove");
from("direct:filemove").to("file://C:/factures/out");

Un endpoint direct se comporte de manière synchrone et fonctionne donc en mode In/Out. Le producteur et le consommateur s’exécutent dans le même thread. Ce endpoint n’a pas d’option et son utilisation n’est jamais plus compliquée que dans l’exemple ci-dessus.
seda :
A l’inverse du endpoint direct, seda se comporte de manière asynchrone et fonctionne donc en mode In Only. Le producteur et le consommateur s’exécutent dans deux threads distincts. Cela permet de paralléliser la consommation sur le endpoint. L’option concurrentConsumers permet de spécifier le nombre de consommateurs en concurrence sur le endpoint. L’implémentation de seda se base sur une BlockingQueue pour la gestion de l’asynchronisme, tout comme le endpoint vm qui est d’ailleurs une extension du endpoint seda. La différence entre les deux réside dans le fait que vm permet de communiquer entre plusieurs contextes Camel.

from("file://C:/factures/in").to("seda:process");
from("seda:process?concurrentConsumers=5").to("file://C:/factures/out");

Dans cet exemple, plusieurs consommateurs travaillent en parallèle. Pour en avoir le cœur net, il est possible d’ajouter des traces de log sur les routes à l’aide du composant log.

from("file://C:/factures/in")
		.to("log:Producteur")
		.to("seda:process");
from("seda:process?concurrentConsumers=5")
		.to("log:Consommateur")
		.to("file://C:/factures/out");

Exécutons le programme et plaçons 5 fichiers dans le répertoire in simultanément, puis observons les traces produites lors de l’exécution. On constate en regardant les identifiants des threads que la consommation est parallèlisée comme nous le souhaitions.

25637 [Camel (camelContext) thread #0 - file://C:/factures/in] INFO Producteur - Exchange[ExchangePattern:InOnly, BodyType:org.apache.camel.component.file.GenericFile, Body:[Body is file based: GenericFile]]
25641 [Camel (camelContext) thread #0 - file://C:/factures/in] INFO Producteur - Exchange[ExchangePattern:InOnly, BodyType:org.apache.camel.component.file.GenericFile, Body:[Body is file based: GenericFile]]
25641 [Camel (camelContext) thread #4 - seda://process] INFO Consommateur - Exchange[ExchangePattern:InOnly, BodyType:org.apache.camel.component.file.GenericFile, Body:[Body is file based: GenericFile]]
25642 [Camel (camelContext) thread #0 - file://C:/factures/in] INFO Producteur - Exchange[ExchangePattern:InOnly, BodyType:org.apache.camel.component.file.GenericFile, Body:[Body is file based: GenericFile]]
25642 [Camel (camelContext) thread #0 - file://C:/factures/in] INFO Producteur - Exchange[ExchangePattern:InOnly, BodyType:org.apache.camel.component.file.GenericFile, Body:[Body is file based: GenericFile]]
25643 [Camel (camelContext) thread #0 - file://C:/factures/in] INFO Producteur - Exchange[ExchangePattern:InOnly, BodyType:org.apache.camel.component.file.GenericFile, Body:[Body is file based: GenericFile]]
25645 [Camel (camelContext) thread #5 - seda://process] INFO Consommateur - Exchange[ExchangePattern:InOnly, BodyType:org.apache.camel.component.file.GenericFile, Body:[Body is file based: GenericFile]]
25645 [Camel (camelContext) thread #1 - seda://process] INFO Consommateur - Exchange[ExchangePattern:InOnly, BodyType:org.apache.camel.component.file.GenericFile, Body:[Body is file based: GenericFile]]
25646 [Camel (camelContext) thread #3 - seda://process] INFO Consommateur - Exchange[ExchangePattern:InOnly, BodyType:org.apache.camel.component.file.GenericFile, Body:[Body is file based: GenericFile]]
25646 [Camel (camelContext) thread #2 - seda://process] INFO Consommateur - Exchange[ExchangePattern:InOnly, BodyType:org.apache.camel.component.file.GenericFile, Body:[Body is file based: GenericFile]]

Il existe une autre possibilité pour paralléliser des traitements sans utiliser de endpoint de type seda. Il s’agit de la méthode threads().

from("file://C:/factures/in")
		.threads(5)                     // Maximum 5 threads
		.to("file://C:/factures/out");

La différence fondamentale entre cette approche et l’usage d’un endpoint de type seda réside dans le fait qu’ici Camel va utiliser un pool de threads dont la taille est variable en fonction de l’activité sur cette route, alors qu’avec seda le nombre de consommateurs – et donc de threads – est fixé au départ et reste inchangé au cours de l’exécution.

Effectuer des traitements

Nous avons vu les bases du routage avec Camel nous permettant de faire transiter des messages de endpoint en endpoint. Mais le routage n’est qu’une des composantes permettant de résoudre la plupart des problématiques d’intégration. Nous aurons également besoin d’effectuer des traitements.
Pour cela, nous allons voir deux manières d’implémenter des traitements :

les processeurs

le composant bean

Les processeurs
Un processeur est une classe qui implémente l’interface Processor. Cette interface définie une unique méthode process qu’il faut donc implémenter.
Par exemple :

package com.zenika.camel.samples.beans;
public class FileProcessor implements org.apache.camel.Processor {
	void process(Exchange exchange) throws Exception {
		System.out.println("FileProcessor.process()");
	}
}

le paramètre exchange de la méthode nous permet entre autres d’accéder au message et éventuellement de le modifier. Ici nous faisons un simple println donc le message en sortie du processeur restera inchangé.
Déclaration du bean dans le contexte Spring

<bean id="fileProcessor" class="com.zenika.camel.samples.beans.FileProcessor" />

Nous pouvons ensuite faire transiter nos messages par ce bean.

from("file://C:/factures/in")
		.processRef("fileProcessor")
		.to("file://C:/factures/out");

Le composant « bean »
Le type de endpoint bean va nous permettre de router des messages vers une classe
java qui implémente le traitement à effectuer.
Contrairement au processeur vu précédemment, cette classe n’a nullement besoin d’implémenter une interface précise ni de respecter un certain format pour les signatures des méthodes.
Commençons par créer une classe qui déclare une méthode.

package com.zenika.camel.samples.beans;
public class FileBean {
	public void processFile() {
		System.out.println("FileBean.processFile()");
	}
}

Déclaration du bean dans le contexte Spring

<bean id="fileBean" class="com.zenika.camel.samples.beans.FileBean" />

Nous pouvons ensuite faire transiter nos messages par ce bean

from("file://C:/factures/in")
	.to("bean:fileBean?method=processFile")
	.to("file://C:/factures/out");

On peut aussi invoquer le bean – de manière équivalente – comme suit

.beanRef("fileBean", "processFile")

Ou encore invoquer un bean en passant sa référence plutôt qu’un id Spring.

.bean(new FileBean(), "processFile")

Dans cet exemple, nous fournissons à Camel le nom de la méthode que nous souhaitons invoquer. En réalité, ce paramètre est optionnel. Dans le cas ou le nom de la méthode n’est pas renseigné, Camel se base sur plusieurs euristiques pour tenter de déterminer quelle est la méthode à invoquer. Je n’entre pas dans le détail du mécanisme mis en place par Camel pour effectuer le choix de la méthode, il est détaillé dans la documentation.
Laisser Camel déterminer la méthode à invoquer n’est pas forcément recommandé. En cas d’évolutions futures du bean par exemple – ajout ou modification d’une signature – il peut être difficile de prévoir les impacts sur le choix qui sera fait par Camel au cours de l’exécution. Personnellement, je ne recommande pas d’utiliser cette possibilité si elle n’apporte pas de valeur ajoutée.

A suivre…

Dans un prochain article, nous étudierons quelques uns des EIP (Enterprise Integration Patterns) les plus courants. Les EIP fournissent des solutions à des problématiques récurrentes dans de nombreux chantiers d’intégration de systèmes.
Camel implémente un grand nombre d’EIP. Nous verrons que leur utilisation apporte une réelle valeur ajoutée lorsqu’il s’agit d’implémenter des logiques de routage complexes.

2 réflexions sur “Introduction au framework Camel – Partie 2

  • Et bien, bravo, vos deux parties concernant Camel m’ont donné envie de m’intérresser a cet « outil », j’attends avec impatience la suite.

    merci.

    Répondre
  • sylvain_priser

    merci pour ces articles très clairs, vivement le 3ème volet !

    Répondre

Répondre à nosfe_Annuler la réponse.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

En savoir plus sur Blog Zenika

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Continue reading