Tutoriel Mule ESB et Application SOA SOAP/REST

Arthur Bamy : Developpeur JAVA chez DigitasLBi

Le but de cet article n'est pas d'expliquer les innombrables manières d'utiliser l'ESB, mais plutôt de détailler un exemple concret d'utilisation dans un écosystème entreprise. Avant toute chose, je vous recommande la lecture de cet article qui illustre parfaitement à quel besoin répond l'ESB dans un contexte de plate-forme web moderne.

Nous avons fait le choix d'utiliser le produit Mule ESB de la société MuleSoft mais sachez que ces concepts sont communs aux ESB en général.

Cet article est fourni par les équipes de DigitasLBi.

N'hésitez pas à commenter cet article ! Commentez

- Titre : Tutoriel Mule ESB et Application SOA SOAP/REST

- Auteur : Arthur Bamy : Developpeur JAVA chez DigitasLBi

- Parution : 3 février 2014

- Licence : Copyright ® 2014 Arthur Bamy. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Voir également :

- Introduction sur les ESB dans le domaine du développement web

Sommaire

- I. Introduction

- I-A. De quoi va-t-on parler?

- I-B. PréPrérequis

- I-C. Le contexte

- I-C-1. Mule ESB isole le client de l'API (il joue un rôle d'adapter)

- I-C-2. Mule ESB est capable d'exposer une même API sous de multiples formats (SOAP, REST, etc.)

- I-D. Outils de développement

- II. Principe de fonctionnement de Mule

- II-A. Les flows

- II-A-1. 1. L'inbound endpoint

- II-A-2. Le flow-ref

- II-A-3. La couche transport

- II-A-4. Conclusion

- II-B. Le Mule Message Object

- II-B-1. Le Header

- II-B-2. Le Payload

- II-B-2-a. Comment fait-on pour stocker autre chose que des chaînes de caractères dans notre Payload ?

- II-B-2-b. Comment fait-on si on a besoin de conserver des informations contenues dans notre Payload ?

- III. Notre Application Mule

- III-A. A. Mise en place du projet

- III-B. Exposition de l'interface soap

- III-C. Implémentation du service

- III-D. Exposition de l'API

- III-E. Adapters

- III-F. Génération de l'application MULE

- IV. Conclusion

- V. Remerciements de l'auteur

- VIII. Remerciements Developpez

I - Introduction

I-A - De quoi va-t-on parler?

Le but de cet article n'est pas d'expliquer les innombrables manières d'utiliser l'ESB, mais plutôt de détailler un exemple concret d'utilisation dans un écosystème entreprise. Avant toute chose, je vous recommande la lecture de cet article qui illustre parfaitement à quel besoin répond l'ESB dans un contexte de plate-forme web moderne.

Nous avons fait le choix d'utiliser le produit Mule ESB de la société MuleSoft mais sachez que ces concepts sont communs aux ESB en général.

I-B - PréPrérequis

Ce tutoriel s'adresse aux personnes ayant déjà des notions de Java EE et de ses principaux outils : Maven, Spring et SOAP. Java JDK 1.6 est requis pour utiliser l'IDE de MuleSoft.

I-C - Le contexte

Voici la première problématique : on expose une façade SOAP à un client tiers. Notre façade est reliée à la couche API V1. Nous devons migrer vers la nouvelle API V2 qui comprend des modifications substantielles du modèle de données sans pour autant modifier l'interface exposée afin que la migration soit transparente pour notre client tiers.

L'ESB se prête tout particulièrement à cet exercice. En effet, il s'intercale parfaitement entre le client tiers et l'api en lieu et place de l'ancienne façade SOAP, de manière à prendre en charge toute l'adaptation entre l'API V1 et l'API V2.

De cette manière le fichier WSDL et plus globalement le « contrat » reste inchangé.

I-C-1 - Mule ESB isole le client de l'API (il joue un rôle d'adapter)

Autre problématique à laquelle nous devons répondre : SOAP est un protocole qui a ses avantages et inconvénients, c'est indéniable, aussi certains clients préfèrent partir sur des architectures plus légères et plus souples telles que REST (application mobile).

Il faut donc que nous soyons en mesure d'exposer une interface REST à notre client en impactant le moins possible notre API.

I-C-2 - Mule ESB est capable d'exposer une même API sous de multiples formats (SOAP, REST, etc.)

On peut imaginer la découverte d'un nouveau besoin qui nous amène à rajouter des connexions avec d'autres clients et protocoles de communication différents.

Mule ESB apporte cette flexibilité.

Architecture actuelle sans ESB :

Architecture après mise en place d'un ESB :

Remarquez que la différence de couleur n'est pas anodine.

Sur le premier schéma, l'API est externe et reliée à Internet, tandis que dans le second, l'API est interne et seul l'ESB est relié à Internet.

Je vais détailler pas à pas comment cela est réalisable et vous allez voir, c'est un jeu d'enfant une fois que l'on a intégré le principe de fonctionnement de Mule.

Je ne couvrirai pas l'intégralité des fonctionnalités Mule sachant qu'elles sont nombreuses et en évolution constante, je me contenterai des fondamentaux et des fonctionnalités nécessaires au bon fonctionnement du projet.

Pour ceux qui ont déjà utilisé un minimum Mule ESB et Mule Studio, félicitations, vous pouvez directement passer au Chapitre III - Notre Application Mule.

I-D - Outils de développement

Pour le développement, deux solutions s'offrent à vous :

- Mule Studio qui est une surcouche d'eclipse (ici) ;

- installer le Mule Studio Eclipse plugin sur votre eclipse (ici).

Les deux se valent selon moi. Personnellement, j'ai opté pour la seconde solution par gain de temps, car ainsi je n'ai pas eu besoin de réinstaller/configurer mes plugins.

Un lien intéressant pour commencer sur l'IDE Mule Studio : ici

II - Principe de fonctionnement de Mule

Mule ESB repose sur les principes de message et de flux.

L'application ESB possède un ou plusieurs points d'entrée et de sortie.

Un message est reçu en entrée, manipulé tout au long des flux jusqu'à sa sortie.

Rien de bien compliqué donc… C'est pourtant un outil extrêmement puissant puisqu'il dispose d'énormément de connecteurs (si ce n'est pas déjà fait, je vous conseille vivement la lecture de l'article de mon collègue Maxime Nougarede sur les avantages de Mule ESB).

Exemple de flux

Voici la représentation graphique du flux :

Et voici le code correspondant :

<flow name="main-flow" doc:name="main-flow">
<http:inbound-endpoint host="0.0.0.0" exchange-pattern="request-response" doc:name="HTTP" path="esb-example/1.0/soap" port="9211" />
<cxf:jaxws-service serviceClass="org.example.IPurchaseOrder" doc:name="WS Interface" />

<logger level="INFO" message=" flowVars : #[flowVars]" />
<set-variable variableName="operation" value="#[flowVars.cxf_operation.localPart]" doc:name="Request Setting" />
<logger level="INFO" message=" operation : #[operation]" />
<flow-ref name="#[operation]-flow" />
<logger level="INFO" message=" payload : #[payload]" />
</flow>

<flow name="OrderStatus-flow">
<logger level="INFO" message="into OrderStatus-flow" />
<set-variable variableName="orderDto" value="#[groovy:com.developpez.esb.utils.CXFAdapter.fromGetOrderStatusType(payload)]" />
<set-payload value="#[groovy:remoteOrderManagementClient.getOrderStatus(orderDto)]" />
<logger level="INFO" message=" payload : #[payload]" />
<set-payload value="#[groovy:com.developpez.esb.utils.CXFAdapter.toGetOrderStatusResponseType(payload)]" />
</flow>

<flow name="Order-flow">
<logger level="INFO" message="into Order-flow" />
<set-variable variableName="orderDto" value="#[groovy:com.developpez.esb.utils.CXFAdapter.fromPurchaseOrderType(payload)]" />
<set-payload value="#[groovy:remoteOrderManagementClient.getOrderConfirmation(orderDto)]" />
<logger level="INFO" message=" payload : #[payload]" />
<set-payload value="#[groovy:com.developpez.esb.utils.CXFAdapter.toOrderConfirmationType(payload)]" />
</flow>

1re remarque : la représentation graphique est selon moi juste un indicateur de la conformité de votre flux, mais ne prenez pas l'habitude de « développer » avec.

L'ensemble de la programmation Mule sera fait sous format xml.

Cet exemple est intéressant, car il contient la plupart des briques les plus utilisées de Mule. À la fin de ce tutoriel, il devrait vous être très facile de le comprendre.

Maintenant, rentrons un peu dans le détail…

II-A - Les flows

Nous avons tout d'abord un flow défini par son nom : name="com.developpez.flux".

Ce flow contient une suite d'opérations qui ont pour but de modifier le message courant ou Payload. Ce message peut prendre des formes diverses, être enrichi, modifié ou écrasé par un autre. Nous allons y revenir par la suite.

Un flow peut avoir différents points d'entrée.

II-A-1 - 1. L'inbound endpoint

Bloc très important puisqu'il sert de point d'entrée pour l'application entière. Dans notre exemple, cela correspond à l'URL d'appel de notre application

<http:inbound-endpoint host="0.0.0.0" exchange-pattern="request-response" doc:name="HTTP" path="${esb.remote.path}/${esb.version}/soap"
port="${esb.remote.port}" connector-ref="HTTP_HTTPS" />

- host : 0.0.0.0 correspond à l'ensemble des interfaces réseau de la machine ;

- port et path comme leur nom l'indique ;

- exchange pattern : dans le cas d'un pattern « request-response », une réponse est automatiquement renvoyée à la fin du flow. C'est le comportement par défaut. On aurait pu avoir exchange-pattern="one-way", auquel cas les messages auraient été envoyés de manière asynchrone.

En chargeant un fichier properties où les variables ci-dessus sont définies, cela donnerait : exam

Si votre application est déployée sur votre serveur http://monappli.dev.fr

Votre application est visible depuis l'URL http://monappli.dev.fr:9211/esb-example/1.0/soap

Cette URL sera donc le endpoint de notre client SOAP.

N.B. : Il y a beaucoup de manières de configurer un inbound endpoint, je vous conseille d'aller faire un tour dans la documentation suivante.

II-A-2 - Le flow-ref

Un flow peut également être appelé par un autre flow à l'aide d'une référence par nom comme ceci :

<flow name="com.developpez.flux" doc:name="com.developpez.flux">
<flow-ref name="another.flux" doc:name="Flow Reference"></flow-ref>
</flow>

<flow name="another.flux" doc:name="another.flux">
<logger message=" payload : #[payload]" doc:name="Logger"></logger>
</flow>

Voici un schéma regroupant les deux manières d'entrer dans un flux :

Et voici le code correspondant :

<http:connector name="HTTP_HTTPS" validateConnections="true" enableCookies="true" doc:name="HTTP\HTTPS" />
<flow name="Flow-A" doc:name="com.developpez.flux">
<http:inbound-endpoint host="0.0.0.0" exchange-pattern="request-response" doc:name="HTTP" path="esb-example/1.0/soap"
port="9211" connector-ref="HTTP_HTTPS" />
<flow-ref name="Flow-B" doc:name="Flow Reference" />
<logger message="return payload : #[payload]" doc:name="Logger" />
</flow>

<flow name="Flow-B" doc:name="another.flux">
<set-payload value="hello" />
<logger message=" payload : #[payload]" doc:name="Logger" />
</flow>

Ainsi, si j'appelle l'URL http://localhost:9211/esb-example/1.0/soap

Ma page affiche : hello.

Récapitulons les différentes étapes de notre application :

1. Notre inbound endpoint est mappé sur le localhost port 9211 avec le path esb-example/1.0/soap avec un pattern d'échange request-response (i.e. L'échange de message se fait en synchrone). Nous référençons un connecteur http, déclaré plus tôt (Ce dernier supporte à la fois http et https).
On peut aller plus loin dans la configuration du connector  HTTP : ici.

2. L'URL que nous appelons match. On entre donc dans le flow Flow-A.

3. Puis on passe dans le Flow-B par référence. Dans ce second flow, on définit la valeur du payload à « hello » avant de revenir dans le Flow-A.

4. Lorsqu'on arrive à la fin du Flow-A, le message est automatiquement renvoyé et affiché par le client.

II-A-3 - La couche transport

Pour passer d'un flow à un autre, il existe une seconde solution : utiliser la couche transport. Cette méthode peut s'avérer indispensable si vous avez besoin de faire communiquer entre elles plusieurs applications Mule ESB. Pour ce faire, tout un ensemble de connecteurs est disponible via Mule ESB (TCP, UDP, FTP, POP3, JMS…)

Documentation ici.

Voici un exemple d'architecture possible :

Si vous voulez mettre en place ce type d'architecture, voici le lien vers la documentation mule :

suivante.

II-A-4 - Conclusion

Cette notion de flow est primordiale dans la compréhension de Mule ESB. Donc, si ça n'est toujours pas clair pour vous, prenez un peu de temps avant de passer à la suite du tutoriel.

Par défaut, Mule Studio contient pas mal de projets d'exemple de type hello-world assez bien faits.

Nous allons maintenant aborder de manière non exhaustive les méthodes pour modeler le message selon nos besoins.

II-B - Le Mule Message Object

Depuis le début, je parle de Message et de Payload. Mais qu'est-ce que c'est exactement ?

Dans Mule, le message est un ensemble de données stockées dans un objet qui va transiter tout au long de l'application, passant de flow en flow.

Voici sa représentation dans la documentation de MuleSoft :

Je vous conseille, encore une fois, de lire la documentation :

suivante.

Pour résumer : le message Object contient le message ainsi que différentes variables. Le message est constitué de deux parties distinctes, le Header et le Payload.

II-B-1 - Le Header

Le header contient les propriétés suivantes :

- les inbound properties proviennent du message en entrée. En pratique, lorsque vous arrivez d'une source, votre message contient des meta-données… Ces propriétés sont immuables. Vous pouvez y accéder mais pas les modifier ni les effacer ;

- les outbound properties sont similaires aux inbound properties mais sont modifiables et sont définies dans le corps du flow par opposition à ces dernières qui sont automatiquement définies dès l'entrée du flow ;

- voici un exemple :

<set-property propertyName="timeStamp" value="#[system.currentTimeMillis]" doc:name="Property"/>

À noter que les outbound properties restent des outbound properties lorsque l'on passe d'un flow à un autre par référence (ie flow-ref). Lorsque l'on passe par le transport, une outbound property est convertie en inbound property.

II-B-2 - Le Payload

C'est le cœur du message. Pour faire une analogie avec le Java traditionnel, c'est à la fois l'objet en entrée de votre méthode et l'objet retourné par la méthode. L'objectif étant de lui affecter la valeur voulue entre l'entrée et la sortie.

Il existe différentes manières de modifier le Payload, la plus simple est d'utiliser le set-payload :

<set-payload value="helloworld" />

Donc le payload est remplacé/écrasé par la chaîne de caractères « helloworld. » Deux questions se posent :

II-B-2-a - Comment fait-on pour stocker autre chose que des chaînes de caractères dans notre Payload ?

Mule ESB permet l'utilisation de langage de scripting tel que Groovy, Ruby, javascript. Il a surtout son propre langage, le Mule Expression Language ou MEL, reconnaissable par ses délimiteurs

Documentation en ligne : ici.

Ainsi, il est très facile d'intégrer des morceaux de script soit dans le set-payload :

<set-payload value="#[groovy:com.developpez.esb.utils.CXFAdapter.toDto(payload)]" doc:name="Set Payload" />

Vous voyez dans cet exemple que l'on fait appel à une classe Java très simplement.

Il est en revanche nécessaire de vérifier que le type de l'objet Payload est bien celui attendu par notre méthode.

Soit à l'intérieur du flow :

<flow name="com.developpez.flux" doc:name="com.developpez.flux">
<http:inbound-endpoint host="0.0.0.0" exchange-pattern="request-response" doc:name="HTTP" path="esb-example/1.0/soap" port="9211"
connector-ref="HTTP_HTTPS" />
<flow-ref name="another.flux" doc:name="Flow Reference"></flow-ref>
<scripting:component doc:name="Groovy">
<scripting:script engine="Groovy">
<scripting:text><![CDATA[
if (payload instanceof java.lang.Throwable) {
throw payload;
}
payload = com.developpez.esb.Dto("hello");
]]></scripting:text>
</scripting:script>
</scripting:component>
</flow>

En sortie du flow, on teste si ce dernier a lancé une erreur. Si c'est le cas, on lève l'exception pour la traiter ultérieurement via une stratégie d'erreurs (voir ici), sinon on continue. La logique de programmation est similaire au java.

II-B-2-b - Comment fait-on si on a besoin de conserver des informations contenues dans notre Payload ?

En effet, on a vu que le payload changeait constamment de valeur et de type, on a forcément besoin à un moment donné de conserver la valeur de ce dernier avant un changement d'état.

La réponse est : par l'utilisation de variables.

Comme vu sur le schéma précédent, le Mule Message Object contient également des variables : voyons quelles sont leurs portée et utilité.

Il existe deux types de variables :

- les Flow variables qui n'existent que dans le flow dans lequel elles sont définies. Elles peuvent servir par exemple pour sauvegarder temporairement la valeur du payload. En revanche, la valeur est perdue après un passage par une couche transport ;

<set-variable variableName="temp" value="#[payload]" doc:name="Request Setting" />

- les Session variables qui sont disponibles tout au long de l'application. Elles sont analogues à des variables globales au sein d'une instance de flow. Elles conservent leur valeur malgré le passage par une couche transport.

<set-session-variable variableName="userId" value="#[message.inboundProperties.userId]" doc:name="UserId Setting" />

Maintenant que les principaux concepts de Mule sont connus, revenons à l'objectif principal : notre application Mule.

III - Notre Application Mule

Rappelons l'objectif : nous allons mettre à disposition du client tiers une interface SOAP identique à celle exposée par notre première version de l'API. Autrement dit, la description du web service reste la même.

III-A - A. Mise en place du projet

La première étape consiste à créer un nouveau projet Mule (le projet associé à cet article est « mavenisé »).

Rapatrions notre fichier wsdl dans le projet ESB :

Voici à quoi doit ressembler votre projet à cette étape :

Ensuite, il est temps de générer notre partie serveur à l'aide du plugin adéquat.

J'ai utilisé ici cxf-codegen-plugin (ici)

L'utilisation du plugin peut être un peu « touchy » au premier abord ; allez donc jeter un coup d'œil au pom.xml si besoin.

Vous devriez obtenir le webservice suivant :

package org.example;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.Action;
import javax.xml.ws.FaultAction;

/**
* This class was generated by Apache CXF 2.5.1
* 2014-01-07T16:36:10.741+01:00
* Generated source version: 2.5.1
*
*/
@WebService(targetNamespace = "http://example.org", name = "IPurchaseOrder")
@XmlSeeAlso({ObjectFactory.class})
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)

public interface IPurchaseOrder {

@WebResult(name = "OrderConfirmationType", targetNamespace = "http://example.org", partName = "parameters")
@Action(input = "http://example.org/purchaseorder", output = "http://example.org/orderconfirmation")
@WebMethod(operationName = "Order", action = "http://example.org/purchaseorder")
public OrderConfirmationType order(
@WebParam(partName = "parameters", name = "PurchaseOrderType", targetNamespace = "http://example.org")
PurchaseOrderType parameters
);

@WebResult(name = "GetOrderStatusResponseType", targetNamespace = "http://example.org", partName = "parameters")
@Action(input = "http://example.org/getorderstatus", output = "http://example.org/getorderstatusresponse", fault = {@FaultAction(className = OrderNotFoundFault.class, value = "http://example.org/ordernotfound")})
@WebMethod(operationName = "OrderStatus", action = "http://example.org/getorderstatus")
public GetOrderStatusResponseType orderStatus(
@WebParam(partName = "parameters", name = "GetOrderStatusType", targetNamespace = "http://example.org")
GetOrderStatusType parameters
) throws OrderNotFoundFault;
}

Une fois les sources générées, on va pouvoir s'attaquer à la partie Mule à proprement parler.

III-B - Exposition de l'interface soap

Un lien qui pourra vous être utile :

ici.

Ouvrez le fichier {nom-de-votre-projet}.xml. C'est dans ce fichier que se trouvera tout notre code métier.

N.B : vous pouvez ajouter autant de fichiers xml que nécessaire pour la séparation et lacompréhension du code. Pour notre exemple, celui-ci suffira.

Comme nous l'avons vu dans le 1er chapitre, la première chose à faire est de créer un flow ainsi qu'un point d'entrée pour ce flow. Ici, le point d'entrée sera également le endpoint de notre client SOAP.

<flow name="main-flow" doc:name="main-flow">
<http:inbound-endpoint host="0.0.0.0" exchange-pattern="request-response" doc:name="HTTP" path="esb-example/1.0/soap"
port="9211" />
<cxf:jaxws-service serviceClass="org.example.IPurchaseOrder" doc:name="WS Interface" />
</flow>

Avec ces quatre lignes de code, on vient de déclarer notre flow principal, le point d'entrée de notre application et le service que l'on souhaite utiliser.

Vous pouvez lancer votre application Mule en faisant un Run as >  Mule Application With Maven.

N.B : Je préfère utiliser cette méthode plutôt qu'uniquement Mule Application, car elle permet de lancer plusieurs applications Mule en local.

Si vous avez des erreurs, vérifiez d'avoir déclaré tous les namespaces nécessaires.

En lançant votre navigateur préféré, vous devriez pouvoir atteindre le wsdl à cette URL  : http://localhost:9211/esb-example/1.0/soap?wsdl

III-C - Implémentation du service

L'interface SOAP est accessible par le client. Bon, bien sûr, il reste à implémenter tout ça.

Vous pouvez essayer d'appeler les méthodes avec votre SOAP-UI, vous devriez voir apparaître de belles exceptions dans la console de votre Mule Studio. C'est normal, car pour l'instant on se contente de rentrer dans le flow et d'en ressortir. Donc le message d'entrée est le même que celui en sortie. Ce n'est pas vraiment ce à quoi votre webservice s'attend.

La technique, dans un premier temps, consiste à créer autant de flows que vous avez de web méthodes et de les nommer de la même manière.

Dans notre cas, nous avons deux méthodes, donc deux nouveaux flows à créer.

Nous allons nous servir de variables du flow courant qui proviennent de l'appel SOAP.

Lancez un appel après avoir rajouté ce log :

<logger message=" flowVars : #[flowVars]" />

Vous devriez voir apparaître ceci dans vos logs:

flowVars : {cxf_service={http://example.org}IPurchaseOrderService, method=public abstract org.example.OrderConfirmationType org.example.IPurchaseOrder.order(org.example.PurchaseOrderType), cxf_operation={http://example.org}Order}

flowVars est l'équivalent d'une map contenant plusieurs informations sur le flow courant. Ici, ce qui nous intéresse, c'est la dernière partie cxf_operation. Elle contient le nom de la méthode à appeler.

cxf_operation = namespace + localPart

Donc on fait comme ceci :

<set-variable variableName="operation" value="#[flowVars.cxf_operation.localPart]" doc:name="Request Setting" />

On vient de créer une variable interne au flow qui contient le nom de notre web méthode. Celle-ci va nous permettre d'aiguiller vers le bon flux, elle fait office de routeur du message :

<flow name="main-flow" doc:name="main-flow">
<http:inbound-endpoint host="0.0.0.0" exchange-pattern="request-response" doc:name="HTTP" path="esb-example/1.0/soap" port="9211" />
<cxf:jaxws-service serviceClass="org.example.IPurchaseOrder" doc:name="WS Interface" />

<logger level="INFO" message=" flowVars : #[flowVars]" />

<set-variable variableName="operation" value="#[flowVars.cxf_operation.localPart]" doc:name="Request Setting" />

<logger level="INFO" message=" operation : #[operation]" />

<flow-ref name="#[operation]-flow" />

<logger level="INFO" message=" payload : #[payload]" />
</flow>

<flow name="OrderStatus-flow">
<logger level="INFO" message="into OrderStatus-flow" />
</flow>

<flow name="Order-flow">
<logger level="INFO" message="into Order-flow" />
</flow>

Vous pouvez tester sur SOAP-UI, vous verrez que chaque web méthode entre dans le flow correspondant.

III-D - Exposition de l'API

Notez que cette partie dépend de l'architecture de votre API. Dans cet exemple, nous avons choisi d'utiliser du Spring Remoting pour effectuer la liaison entre Mule ESB et l'API qui sera portée par un serveur Tomcat. On communique en Java sérialisé, aussi l'ensemble de vos dto doit être serializable.

Plus d'informations : http://docs.spring.io/spring/docs/current/reference/remoting.htmlPlus d'informations ou sur votre site préféré : Developpez.com.

Il y a d'autres solutions possibles, bien entendu.

J'expliquerai ici les principes, je suppose l'existence du service, je me contenterai d'un mock en ce qui concerne les sources (la mise en place d'une API avec Tomcat sort du cadre de ce tutoriel).

Voici à quoi ça ressemble :

Dans la conf Spring de votre Tomcat :

<bean id="remoteOrderManagementService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="orderManagementService" />
<property name="serviceInterface" value="com.developpez.esb.service.api.IOrderManagementService" />
</bean>

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/orderManagementService">remoteOrderManagementService</prop>
</props>
</property>
</bean>

Où orderManagementService est un bean spring du service.

Dans votre web.xml :

<!-- DispatcherServlet servlet -->
<servlet>
<servlet-name>servlet-remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>

Dans le fichier xml de votre application Mule ESB :

<spring:bean id="remoteOrderManagementClient" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<spring:property name="serviceUrl" value="${remote.url}:${remote.port}/${remote.path}/orderManagement" />
<spring:property name="serviceInterface" value="com.developpez.esb.service.api.IOrderManagementService" />
</spring:bean>

Jetez un œil à la documentation de Spring Remoting. Ça n'est pas bien compliqué à mettre en place.

N.B. : n'oubliez pas de mettre à jour votre conf Maven en ajoutant le module qui porte l'interface.

Une fois cette étape effectuée, vous allez pouvoir faire des appels à votre API depuis votre application Mule de la manière la plus simple qui soit :

<set-session-variable variableName="order" value="#[groovy:remoteOrderManagementClient.getUser(orderId)]" />

Pratique !

Revenons maintenant à nos web méthodes et nos flows.

III-E - Adapters

N'oublions que le but était la migration d'une version d'une API à une autre.

Nous avons relié l'ESB au client SOAP et l'ESB à l'API.

Il reste le point central : l'adaptation des beans cxf en bean API et inversement.

Rappelez-vous la signature de l'une de nos web méthodes :

public OrderConfirmationType order(PurchaseOrderType parameters);

voici maintenant la signature de la méthode qui va être appelé au niveau de l'API :

public OrderDto getOrderConfirmation(OrderDto orderDto);

Comme vous pouvez le constatez, ces méthodes n'ont aucun type en commun.

Pour cela, comme nous utilisons ici la version community, je crée une classe CXFAdapter qui va se charger des conversions de type et de la bonne conservation des données (la version entreprise de MuleEsb fournit un outil, le datamapper, qui peut remplir la même fonctionnalité en WYSIWYG).

Approche avec la version Enterprise

En utilisant la version avec CXFAdapter, l'invocation de la web méthode order respecte le modèle suivant :

Voici la classe CXFAdapter :

public class CXFAdapter {
/**
* converts inbound CXF bean purchaseOrderType to API bean orderDto

*
* @param purchaseOrderType
* @return orderDto
*/
public static OrderDto fromPurchaseOrderType(PurchaseOrderType purchaseOrderType) {
OrderDto orderDto = new OrderDto();

orderDto.setProductName(purchaseOrderType.getProductName());
orderDto.setQuantity(purchaseOrderType.getQuantity());

return orderDto;
}

/**
* converts inbound CXF bean getOrderStatusType to API bean orderDto
*
* @param getOrderStatusType
* @return orderDto
*/
public static OrderDto fromGetOrderStatusType(GetOrderStatusType getOrderStatusType) {
OrderDto orderDto = new OrderDto();

orderDto.setOrderID(getOrderStatusType.getOrderID());

return orderDto;
}

/**
* converts API bean orderDto to outbound CXF bean purchaseOrderType
*
* @param orderDto
* @return getOrderStatusResponseType
*/
public static GetOrderStatusResponseType toGetOrderStatusResponseType(OrderDto orderDto) {
org.example.ObjectFactory objectFactory = new ObjectFactory();
GetOrderStatusResponseType getOrderStatusResponseType = objectFactory.createGetOrderStatusResponseType();

getOrderStatusResponseType.setOrderID(orderDto.getOrderID());
getOrderStatusResponseType.setStatus(orderDto.getStatus());

return getOrderStatusResponseType;
}

/**
* converts API bean orderDto to outbound CXF bean orderConfirmationType
*
* @param orderDto
* @return orderConfirmationType
*/
public static OrderConfirmationType toOrderConfirmationType(OrderDto orderDto) {
org.example.ObjectFactory objectFactory = new ObjectFactory();
OrderConfirmationType orderConfirmationType =    objectFactory.createOrderConfirmationType();

orderConfirmationType.setOrderID(orderDto.getOrderID());
orderConfirmationType.setExpectedShipDate(orderDto.getExpectedShipDate());

return orderConfirmationType;
}
}

Voici maintenant nos flux contenant les appels à l'API :

<flow name="OrderStatus-flow">
<logger level="INFO" message="into OrderStatus-flow" />
<!-- convert the payload from org.example.GetOrderStatusType to com.developpez.esb.dto.OrderDto using CXFAdapter utils -->
<set-variable variableName="orderDto" value="#[groovy:com.developpez.esb.utils.CXFAdapter.fromGetOrderStatusType(payload)]" />
<!-- convert the payload from GetOrderStatusType to com.developpez.esb.dto.OrderDto using CXFAdapter utils -->
<set-payload value="#[groovy:remoteOrderManagementClient.getOrderStatus(orderDto)]" />
<logger level="INFO" message=" payload : #[payload]" />
<!-- convert the payload from com.developpez.esb.dto.OrderDto to org.example.GetOrderStatusResponseType using CXFAdapter utils -->
<set-payload value="#[groovy:com.developpez.esb.utils.CXFAdapter.toGetOrderStatusResponseType(payload)]" />
<!-- the payload is redirected to the main flow and then to the soap client -->
<!-- the payload is a org.example.GetOrderStatusResponseType which is the type required by the soap response according to the service -->
</flow>

<flow name="Order-flow">
<logger level="INFO" message="into Order-flow" />
<set-variable variableName="orderDto" value="#[groovy:com.developpez.esb.utils.CXFAdapter.fromPurchaseOrderType(payload)]" />
<set-payload value="#[groovy:remoteOrderManagementClient.getOrderConfirmation(orderDto)]" />
<logger level="INFO" message=" payload : #[payload]" />
<set-payload value="#[groovy:com.developpez.esb.utils.CXFAdapter.toOrderConfirmationType(payload)]" />
</flow>

Notez l'insertion de log à chaque modification du Payload pour être sûr qu'il est bien du type attendu.

Vous pouvez maintenant lancer votre application et tester avec votre client SOAP. Et là, miracle, vous obtenez une réponse conforme.

III-F - Génération de l'application MULE

Maintenant que nous avons fait fonctionner notre application sur notre environnement local, il nous reste à l'utiliser dans un environnement de production.

Pour cela, nous devons packager notre application mule grâce au plugin maven adéquat :

<plugin>
<groupId>org.mule.tools</groupId>
<artifactId>maven-mule-plugin</artifactId>
<version>1.9</version>
<extensions>true</extensions>
<configuration>
<copyToAppsDirectory>true</copyToAppsDirectory>
</configuration>
</plugin>

Exécutez un « maven clean install » et vous obtenez un zip contenant votre appli.

Vous pouvez à présent la déployer sur un mule server standalone ou autre selon votre besoin. Le choix vous appartient. La documentation est ici.

IV - Conclusion

Ainsi s'achève ce tutoriel. Comme promis, je vous ai donné les bases pour développer votre propre application Mule ainsi qu'un exemple d'utilisation dans un contexte de refonte/évolution d'api.

Comme je l'ai souligné plus haut, nous aurions très bien pu proposer une interface en REST. Ça sera peut-être l'objet d'un prochain tutoriel.

V - Remerciements de l'auteur

J'adresse mes remerciements à l'équipe Java de Digitas et plus particulièrement à Cedrik Lime, Romain Dahan, Yvan Saule et Mohamed El-Habib, qui m'ont été d'une grande aide dans l'écriture de ce tutoriel.

VIII - Remerciements Developpez

L'équipe Developpement web tient à remercier DigitasLBi pour ce tutoriel.

Nos remerciements à milkoseck pour sa gabarisation et à ced pour sa correction orthographique.

N'hésitez pas à commenter cet article ! Commentez