Créer sa couche de transport pour Petals ESB en 4 classes


Je profite d’avoir les doigts bien chauds (je viens de passer quelques jours à faire du M$ Word à plein temps, dure reprise après des vacances au grand air…) pour écrire un article que j’ai dans la tête depuis un petit moment : Comment développer sa propre couche de transport pour OW2-Petals ESB. Pour savoir ce qu’est la couche de transport dans Petals, allez jeter un coup d’oeil les précédents articles .

Note : Ce travail se base sur Petals ESB 3.0.2 mais est normalement compatible avec la v3.x. Tout le code est disponible sur le SVN du projet Petals ESB chez OW2 (le lien plus bas) et je ne l’ai pas inséré dans l’article car l’intérêt n’est pas de montrer que je sais coder en Java…

Le contexte

Petals ESB v3.x est livré avec une couche de transport permettant de faire communiquer les instances du bus de service basée sur une implémentation Java NIO. Le but de l’article est de présenter un framework de couche de transport et de montrer que développer une nouvelle couche en se basant sur le framework est simple et rapide.

Le framework

Le but n’est pas de détailler complètement le framework, mais juste les points d’extension qu’il offre. Le framework gère la logique d’envoi synchrone et asynchrone des messages, ce qu’il reste à implémenter est finalement juste la partie permettant de recevoir des messages (ie le serveur) et d’envoyer les messages sur le noeud distant (indépendamment de la logique Petals).

  • Le serveur: Il doit être capable de recevoir un message de la forme de son choix (tout dépend du protocole utilisé) et d’informer le framework qu’un nouveau message est recu après l’avoir converti en message Petals (ici JBI), rien de plus…
  • Le client: Doit pouvoir retrouver le serveur à appeler et lui envoyer le message après avoir transformé le message Petals (JBI) en message serveur-dépendant.

Le  framework est basé sur une abstraction de la couche de transport Java NIO de Petals ESB v3 et son code source est disponible dans ma sandbox dans les projets petals-transport-api et petals-transport.

Une implémentation Web service?

Oui parce que c’est la plus rapide et qu’elle est bien utile! Allez pour faire encore plus simple (et pour ne pas changer une équipe qui gagne), on va utiliser JAXWS & Apache CXF.

1. Le coté serveur

Il est en charge de démarrer le service de réception de messages, ici on va utiliser JAXWS pour exposer une instance de org.ow2.petals.transport.cxf.TransportServiceImpl qui implémente org.ow2.petals.esb.api.TransportService.

L’implémentation cré un message JBI depuis le message reçu par la pile Web service (via la méthode/opération #receive), et passe ce message au Receiver en appelant l’unique méthode #onMessage(MessageExchange). Le receiver n’est ici rien d’autre que le composant de transport du Framework (org.ow2.petals.transport.TransporterImpl dans le module petals-transport) qui implémente le service de transport de Petals ESB (org.ow2.petals.transport.Transporter) et qui possède l’intelligence pour faire remonter le message dans les couches de Petals ESB.

2. Le coté client

Ici aussi, rien de bien méchant. Il suffit de savoir créer un client Web service pour parler au serveur que l’on vient de présenter au point 1. Une fois encore, JAXWS et CXF font ca bien. Une factory est à implémenter (org.ow2.petals.transport.api.ClientFactory) et permet de créer des instance du client pour chaque container Petals ESB à invoquer.

3. What else?

Un peu de configuration via Fractal et le tour est joué. Une nouvelle couche de transport permet de faire communiquer les instances de Petals ESB via Web service au lieu de passer par NIO, tout cela avec quelques classes…

Et alors?

Avec quelques classes (même pas une dizaine, dans le meilleur des cas implémenter 4 interfaces doit être suffisant), on peut créer un nouveau type de transporter pour Petals ESB sans se soucier de :

  1. JBI. Ou presque la seule chose à faire est créer un message JAXWS depuis un message JBI et inversement (ca va il y a plus dur…)
  2. La logique de message synchrone et asynchrone. Tout cela est géré par le framework.
  3. La remontée du message vers le bon fournisseur de service.

On focalise ici vraiment sur la partie communication pure entre client et serveur. Pour valider le principe d’implémentation facile et rapide, j’ai créé un transporteur XMPP en à peu près un jour… Le code de ce transporteur XMPP n’est pas encore public mais ca ne devrait pas tarder. Il me faut juste un peu de temps…

Tout cela pour montrer qu’utiliser Petals ESB n’est pas limitant. L’architecture permet quand même des points d’extension intéressants, il faut juste s’y connaitre un peu (beaucoup). C’est juste notre métier…

Petals ESB over XMPP #2


Dans l’article précédent, je parlais de l’utilisation de XMPP et de Google Talk pour faire communiquer des instances de Petals ESB. L’utilisation était seulement faite au niveau de la couche de transport de Petals (cf cet article sur la couche de transport) et ne couvrait donc pas tous les canaux de communication qui entrent en jeu (et en particulier ceux du registre de services).
Dans la continuité de mes travaux sur la fédération de bus de service, je me suis donc intéressé à la possibilité de faire communiquer des domaines distincts (un domaine étant un ensemble de noeuds au sein d’une institution par exemple) en utilisant XMPP non pas seulement pour la couche de transport mais aussi pour les communications du registre de service.

En pratique, les domaines sont complètement indépendants, ils ne sont aucunement connectés via Internet ou toute autre connection alternative. Dans cette première itération sur la fédération, il n’y a pas de notion de hierarchie en domaine et sous domaine. Un noeud d’un domaine peut demander à la fédération une résolution de endpoint et aussi envoyer un message à des endpoints de la fédération pour invoquer des services. Une vue de haut niveau sur deux noeuds dans deux domaines différents est la suivante :

Techniquement, l’intégration de la fédération intervient à plusieurs niveaux :

  1. Chaque noeud implémente le service de fédération qui permet de donner un point d’entrée à la fédération. Ce service expose une opération de recherche de endpoint et une opération d’invocation de service.
  2. Chaque noeud s’enregistre dans la fédération en donnant un callback (ici c’est le point d’accès au service exposé en 1 qui est publié dans la fédération).
  3. Un module de fédération permet de faire une recherche de endpoint dans la fédération. Dans le cas de Petals, c’est un module du routeur (cf cet article sur les modules) qui permet de faire cette recherche.
  4. Une implémentation au niveau fédération de la couche de transport permet d’aller invoquer un service de la fédération.

Une première approche simple (et pas forcément efficace) est de laisser le module de routage faire une requête à la fédération à chaque invocation pour trouver une liste de endpoints qui peut répondre à la requête, une version plus évoluée permettrait d’avoir un cache à un niveau intermédiaire (pas si évident en fait dans une approche très dynamique). A voir…

En attendant, un premier prototype permets de faire communiquer des noeuds appartenant à des domaines différents sans que ceux ci est une connaissance des autres noeuds (au niveau de Petals ESB, les noeuds des domaines ne sont pas renseignés au niveau du service de topologie). La vidéo qui suit montre deux noeuds, un sur mon laptop et un autre sur un serveur OVH. Je n’ai ouvert aucun port sur ma Box, sur mon laptop ou sur le serveur pour faire passer du trafic supplémentaire : C’est un des avantages à utiliser XMPP.
Le scénario est simple, un client sur mon laptop veut invoquer un service qui fournit une interface X. Cette interface X n’est fournie par aucun service localement (d’ailleurs on voit bien qu’il n’y a pas de endpoint sur le container coté client). Et la magie, il existe un endpoint dans la fédération qui fournit cette interface et il se trouve que cet endpoint est sur le container tournant sur le serveur OVH.
Et alors? Ca a l’air bête comme çà mais on arrive quand même à récupérer la référence du endpoint distant et à invoquer le service sans avoir besoin d’ouvrir quoi que ce soit, de faire du polling ou autre. Merci XMPP.
Comment? Pour dire simple, chaque noeud se connecte sur un serveur XMPP. En pratique c’est un peu plus compliqué et je laisse ca pour un prochain article!

Les classloaders des modules dans Axis2: Les ressources


J’ai expliqué le genre de choses intéressantes que l’on peut faire avec les modules dans la pile Apache Axis2 dans l’article sur le reroutage d’appels de services. J’ai eu besoin aujourd’hui d’aller un peu plus loin dans l’utilisation de ces modules et comme d’habitude, en utilisant ce genre de choses, je partage mes aventures… On va voir rapidement ici comment se comportent les ClassLoaders dans le cas d’Axis2 et des modules.

Et alors?

Je suis en train de développer un module qui reroute les appels de service. L’adresse du service à appeler est donc mis à jour lors de la traversée du module en question. Pour faire bête, je me suis dit que j’aller spécifier un jeu d’adresses dans un bon vieux fichier de propriétés et que j’allais mettre ce fichier dans le module. OK, c’est bien, ça marche (heureusement…), je peux charger mon fichier dans le Handler du module et utiliser les adresses qui y sont définies.

Ce qui m’embête, c’est que ce fichier est packagé dans l’archive du module et donc non modifiable à souhait… C’est là que le Classloader d’Axis2 entre en piste. Si je mets un fichier avec le même nom dans les ressources de mon code client, celui ci est pris en compte en priorité lors de l’appel par le module de MonModule.class.getResource(« /foo.properties »); Je peux donc cette fois ci livrer un module générique qui inclus des valeurs par défaut et les clients qui utilisent ce module peuvent ‘overrider’ les valeurs par défaut dans leur propre fichier de configuration.

Encore une fois, merci Axis2.

L’acheminement de messages dans Petals ESB : #2 Le routage


Intro

Dans l’article précédent sur l’acheminement de messages dans Petals ESB, j’introduisais les notions, aujourd’hui nous allons regarder en détails la couche de routage du bus. Cette couche est largement appelée Router par les développeurs du Bus, et le sera donc dans cette article et dans la suite de la série.

L’archi simplifiée

L’architecture du Router utilise la notion très répandue et  fort utile de modules ie le Router invoque séquentiellement une liste de modules pour élire les services à appeller. A la fin de la traversée on se retrouve avec toutes les informations nécessaires pour appeler les services. On peut modifier cette liste en ajoutant ou supprimant des modules par configuration. Un schéma simplifié donne un Router avec cette tête :

Plus de détails sur l’utilisation des modules dans Petals et leur injection dans un vieil article (à non pas si vieux…).

L’implémentation

L’implémentation de base de Petals ESB est composée de 2 modules en émission :

  1. Le module qui interroge le registre de services et retourne une liste de point d’accès (endpoint dans le jargon service) en fonction des informations contenues dans le message d’entrée.
  2. Le module de résolution de la couche de transport. En fonction de la localisation des endpoints trouvés par le module précédent, un contexte est créé pour définir la couche de transport à utiliser (cette couche de transport sera détaillée dans le prochain article de la série).

On peut imaginer un grand nombre d’utilisations de ces modules. Personnellement, je les utilise à outrance par exemple pour :

  • Ajouter des informations de timestamp sur les messages
  • Notifier une couche de monitoring que des messages sont échangés entre consommateurs et fournisseurs de service
  • Logger les appels
  • Mettre à jour le choix de la couche de transport avec ma propre implémentation du transport de messages inter container

Outro

Vous en savez un peu plus sur le routage dans le bus de service Petals. il faut vraiment retenir que cette couche est vraiment extensible et customisable sans grand effort, en effet pas besoin de recompiler le coeur de Petals pour ajouter des fonctionnalités pour la résolution des endpoints. Dans le prochain article de la série, nous regarderons de plus prêt comment sont échangés les messages entre les instances de Petals.

Axis2 : Rerouter vos appels de services sans modifier vote code client


Imaginons que vous ayez à votre disposition un client de Web service et que pour une raison (qui doit être bonne je l’accorde), vous avez besoin de rerouter vos appels de service vers un nouveau point d’accès. Le problème est que vous n’avez pas le droit de modifier le code client (ou que vous ne l’avez pas), que vous ne voulez pas regénérer ce code client à partir du WSDL du nouveau service a appeler (qui est exactement le même hormis l’adresse du endpoint)… Heureusement, votre client utilise la pile Web service Apache Axis2, et les développeurs d’Axis2 ont gentiment pensé à introduire des modules dans leur architecture. Une bonne intorduction aux modules est disponible dans le guide d’architecture d’Axis2.

Nous allons donc développer un module qui change dynamiquement l’URL de l’endpoint à appeler (on utilisera bien sûr Maven2 pour nous aider à créer et packager le projet…).

1. Créer le projet

Utilisons Maven2 pour créer un projet :

mvn archetype:generate

et je réponds bêtement a Maven quand il me pose des questions sur le groupId, l’artifactId, etc… Une fois créé, je génère le projet Eclipse associé et je l’importe (Eclipse fait aussi parti de mes grand amis) :

mvn eclipse:eclipse

Ill faut modifier le POM pour dire a Maven que l’on est en train de créer un projet qui n’est pas une librarie Java mais un module Axis2. Ceci est possible en spécifiant le type de projet dans le POM et en déclarant que l’on veut utiliser le plugin MAR fournit par Axis2 :


<project ...>

...

<groupId>foo.bar</groupId>
<artifactId>reroute</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>mar</packaging>

...

<dependencies>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-kernel</artifactId>
<version>1.5.1</version>
</dependency>
</dependencies>

...

<build>
<plugins>
<plugin>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-mar-maven-plugin</artifactId>
<version>1.5.1</version>
<extensions>true</extensions>
<configuration>
<moduleXmlFile>src/main/META-INF/module.xml</moduleXmlFile>
<includeDependencies>false</includeDependencies>
</configuration>
</plugin>
</plugins>
</build>
...
</project>

2. Business code

Créons maintenant notre handler (le module est en fait un handler Axis2 packagé dans une archive avec un descripteur de module). Pour faire simple, je vais étendre la classe AbstractHandler pour créer mon Handler de reroutage et remplacer le ‘target EPR’ par le nouveau (bien sûr on peut faire quelque chose de beaucoup plus évolué, on va dire qu’ici tout part vers un seul service) :

package net.chamerling.blog.axis2module.reroute;

import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.handlers.AbstractHandler;
import org.apache.axis2.util.LoggingControl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author chamerling
 *
 */
public class ReRouteHandler extends AbstractHandler {
	private static final Log log = LogFactory.getLog(AbstractHandler.class);

	private static final String FINAL_EPR_PREFIX = "http://localhost:8082/petals/proxy/";

	/**
	 * {@inheritDoc}
	 */
	public InvocationResponse invoke(MessageContext messageContext)
			throws AxisFault {
		EndpointReference toEPR = messageContext.getOptions().getTo();
		String finalEPR = FINAL_EPR_PREFIX + toEPR.getAddress();

		if (LoggingControl.debugLoggingAllowed && log.isDebugEnabled()) {
			log.debug("Reroute Handler for message : "
					+ messageContext.getMessageID());
			log.debug("Initial ToEPR is " + toEPR.getAddress()
					+ " and is changed to " + finalEPR);
		}

		toEPR.setAddress(finalEPR);
		return InvocationResponse.CONTINUE;
	}
}
<pre>

Avec Axis2 c’est ‘facile’, tout est dans le message context! Je viens de remplacer le service à appeler par le mien en modifiant le champ ‘address’ du ‘EndpointReference’.

3. Packager le module

Pour créer un module avec le Handler que l’on vient de définir, il faut passer par l’écriture du fichier de description du module, le ‘module.xml’. Ici c’est simple :

<module name="reroute">
	<OutFlow>
		<handler name="ReRouteHandler" class="net.chamerling.blog.axis2module.reroute.ReRouteHandler">
			<order phase="reroutePhase" />
		</handler>
	</OutFlow>
</module>
<pre>

Mon module s’appellera donc ‘reroute’ et sera appelé que lors de la phase d’émission de message ‘OutFlow’. Plus d’informations sur ce qui est possible avec ce fichier sur le site d’Axis2.

4. Configurer Axis2

Il y a plusieurs façcons de dire à Axis2 d’utiliser le module que l’on vient de développer. En se basant sur la contrainte de départ, je n’ai pas le droit de modifier mes services donc je ne peux pas modifier le descripteur de service ‘service.xml’. Heureusement, j’ai accès au serveur et je peux modifier le fichier de configuration d’Axis2 ‘axis2.xml’ :

...
<!-- Engage the reroute module -->
<module ref="reroute" />
...
<phaseOrder type="OutFlow">
	<!--      user can add his own phases to this area  -->
	<phase name="soapmonitorPhase" />
	<phase name="OperationOutPhase" />
	<!--system predefined phase-->
	<!--these phase will run irrespective of the service-->
	<phase name="PolicyDetermination" />
	<phase name="reroutePhase" />
	<phase name="MessageOut" />
	<phase name="Security" />
</phaseOrder>
<pre>

Je viens d’engager mon module pour tous les services et je l’ai aussi ajouté dans la collection de phases qui est appelé lors de l’émission d’un message. A chaque fois qu’un message est envoyé vers un service, on passe donc dans le module que l’on vient de développer. Je viens donc de modifier les services invoqués sans modifier le code client! Je reviendrais bientôt sur l’utilisation de ce genre de choses dans un vrai cas d’usage avec du REST, du SOAP, des PROXYs, de l’ESB distribué sur le Web (bien sûr avec Petals ESB), etc…

Resources

Le code de ce ‘tutoriel’ est bien sûr disponible sur mon Google Code sous http://code.google.com/p/chamerling/source/browse/#svn/trunk/blog/reroute-axis2.