Un ESB (Enterprise Services Bus) nos facilita interactuar entre dos o más sistemas informáticos y componentes de software de diversos proveedores. Un ESB es una tecnología para integraciones de software que gestiona datos y su comunicación entre diferentes servicios. En este contexto, encontramos que MuleSoft cuenta con su producto Mule ESB y complementarios orientado como plataforma de integraciones, el cual ha ganado popularidad y posicionamiento en el mercado en este tópico.
Para entender mejor el concepto, imagine que un gobierno publica un mecanismo o una API para gestionar la facturación electrónica. Cada empresa debe reportar al gobierno mediante su propio mecanismo o software (o adquirir solución de un tercero especializado). Ahora bien, tienes una aplicación que no cumple con los requisitos de la facturación electrónica y no ves necesario adquirir una solución de terceros porque existen características en tu sistema de gestión que ya hacen parte de la dinámica de tu organización, o prefieres ampliar las características de un producto propio. Así que cuentas con un sistema de gestión y el gobierno pide que la facturación electrónica se entrege bajo sus criterios y mecanismo, por tanto debes cubrir esto con una integración. Aquí aparece en el medio un ESB como el de MuleSoft, con el cual puedes implementar las transformaciones necesarias y comunicar los dos sistemas, reduciendo los tiempos de desarrollo del componente de software.
Mule ESB posee características tecnológicas como la estructura del mensaje (payload, attribute, vars) y flujos que se componen de conectores disponibles (HTTP Listener, HTTP Request, Select Database). Además, incorpora el uso de RAML para especificación de una API (basado en YAML), así como su propio lenguaje DataWeave para transformar datos (siendo un lenguaje sencillo y funcional, con semejanzas a Javascript).
Con la propuesta MuleSoft no sólo integras servicios, sino que también es posible construir tu propia API, y en una alta medida el desarrollo del componente de software se hace de modo visual (drag and drog).
En el presente texto veremos de modo ágil las características esenciales para comenzar con Mule ESB 4 y Anypoint Studio (7+).
Una forma de comenzar con MuleSoft es decargar la herramienta Anypoint Studio (un IDE gráfico basado en Eclipse). Cuando inicias encontrarás en el proyecto los siguientes elementos:
.xml
que contienen los componentes y flujos del proyecto se ubican en: src/main/mule/
(*.xml
)src/main/resources
pom.xml
)mule-artifact.json
(el cual indica la versión de Mule)xml
de la vista gráfica)Esta herramienta ya incorpora Mule ESB Server 4 en su edición empresarial (EE)
Anypoint Studio nos permite desarrollar aplicaciones o componentes que correrán en Mule ESB Server, y esto lo hace de un modo visual. Sin embargo, podemos encontrar que internamente se genera un archivo xml
.
Para representar en xml
lo que se hace con Anypoint Studio, veamos el contenido de un ejemplo esencial:
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd">
<http:listener-config name="HTTP_Listener_config"
doc:name="HTTP Listener config" doc:id="a2665ce3-9e1f-4a9a-b802-e8a531d1729a" >
<http:listener-connection host="0.0.0.0" port="8081" />
</http:listener-config>
<flow name="mytestFlow" doc:id="6da5d990-6097-4bb1-9df7-709503c8b3fd" >
<http:listener doc:name="Listener"
doc:id="3b8a0a1f-482c-47f2-a504-2436de5037bb" config-ref="HTTP_Listener_config" path="/hello"/>
<set-payload value="Hi there from Mule!" doc:name="Set Payload" doc:id="745f1b45-ae81-4730-b7bd-3a8a2d86872d" />
</flow>
</mule>
Listener
~>
Set Payload
En este ejemplo, se configura un Listener
(para exponer un servicio en el puerto 8081) y se tiene un flujo con ese conector (Listener
). Además, se usa el conector Set Payload
(para establecer un valor).
Podemos consultar en un navegador la dirección
localhost:8081/hello
para ver el resultado, que correspondería al mensaje:Hi there from Mule!
Podríamos agregar luego un Logger
para ver también el mensaje en consola, y así vamos definiendo nuestro flujo. Lo que se logra con este ejercicio es demostrar una funcionalidad sencilla pero operando sin escribir código alguno.
payload
)attributes.queryParams
, attributes.uriParams
, attributes.requestPath
vars.
Cuando se asigna el valor al
payload
o a unavariable
se usa una cadena o una expresión. las expresiones e definen entre los siguientes caracteres:#[...]
Los conectores son componentes o módulos que hacen parte del flujo de una aplicación con Mule y pueden servir para acceder a un recurso o sistema, transformar un mensaje, hacer una petición web, acceder a la base de datos, o interactuar con alguna integración disponible (ej. AWS, Salesforce). A continuación se describen una serie de conectores.
Listener (HTTP): Expone un puerto y ruta para gestionar peticiones que son enviadas por el usuario. Se debe ubicar en la parte de source
de un flujo y se configura: host
, port
, path
Request (HTTP): Realiza peticiones HTTP a una API, es decir, invoca servicios web. Se ubica en la parte de process
y se configura: protocol
, method
url/path
. Puede enviarse: body
, headers
, Attributes
, QueryParams
Logger: Permite mostrar un mensaje en consola y puede basarse en una expresión.
Set Payload: Permite actualizar el payload
del mensaje. Puede ser una cadena o una expresión en el lenguaje DataWeave (attributes.queryParams
, attributes.uriParams
)
Set Variable: Establece una variable con un valor para ser gestionada en los flujos mediante el objeto vars.
. Además, variable se puede borrar con el conector Remove Variable
Flow Reference: Hace posible invocar otro flujo por su nombre. Si se usa este conector debe reportarse uno de los flujos disponibles en el proyecto.
Transform Message: Permite transformar el mensaje usando el lenguaje DataWeave. Generamente se hace referencia a payload
, variable
o attributes
. Por defecto su salida es: output application/java --- {}
(debe tenerse presente si debe cambiarse, por ejemplo, application/json
)
Database (Select, Insert, Update, Delete): El conector Select
es uno de los componentes para acceder a bases de datos SQL y returna un Array
. Se debe configurar la conexión, la cual requiere driver
, host
, port
, user
, password
, database/service
. Del msimo modo, encontramos los conectores Insert
, Update
, y Delete
. En cualquier caso la sentencia se establece en SQL Query text
.
APIKit (Router): Funciona como un enrrutador de rutas de una API haciendo referencia a cada flujo posible (GET /users
, POST /users
, etc.). Es posible importar una especificación de una API a partir de RAML y generar este conector con sus flujos automáticamente. Además, con el conector APIKit Console
se puede consultar la API y probarla (sin necesidad de PostMan
u otro software).
Choice: Se usa para encaminar los mensajes a través del flujo dada una condición. Es decir, establece un punto de validación para determinar posibles caminos en un siguiente paso. Siempre se determina un camino, basándose en la primera expresión que cumple.
File (Read, Write): Se usa para acceder a un archivo local en una ruta específica.
SFTP: Se usa para acceder a un archivo remoto indicando host
, port
, username
y password
.
Email: Es un conector extra que se usa para enviar un correo electrónico.
Schedule: Permite agendar un trabajo cada cierto intervalo de tiempo. Se debe ubicar en la parte de source
de un flujo y se configura el tiempo.
Scatter-Gather: Ejecuta rutas concurrentes en lugar de hacerlo sencuencial y establece un punto de espera hasta que se terminen de ejecutar los componentes asociados.
Validation: Es un conector extra que permite verificar expresiones sobre el contenido del mensaje y evaluar si cumple un criterio, por ejemplo, un formato de correo electrónico.
Java: Es un conector extra que permite invocar métodos de una clase de Java.
Si conoces acerca de YAML (un lenguaje de marcado para datos) entonces ya tienes las bases para RAML, el cual busca especificar una API bajo su estándar. Esta especificación se puede usar en Anypoint Studio (MuleSoft) para generar automáticamente el flujo con los conectores iniciales, además de generar documentación sobre la API y lograr algo funcional.
Recomiendo acceder a una introducción sobre RAML
Como noción general, es importante tener presente las características esenciales de un archivo RAML:
get
, post
, put
, delete
, etc.body
, queryParams
, uriParams
, headers
body
y a un tipo, por ejemplo, application/json
, application/xml
.Tendríamos que agregar que el archivo inicial (o
root
) debe tener el atributotitle
y la primera línea debe contener la expresión#%RAML 1.0
(que se refiere a la versión de RAML)
Un archivo sencillo, pensando en una API para gestionar posts de un Blog, puede comprender lo siguiente:
#%RAML 1.0
title: myapi
/posts:
get:
responses:
200:
body:
application/json:
example:
{ "id": "1", "title": "My Article" }
400:
body:
application/json:
example:
{ "message": "Not found" }
Este archivo se conoce como
root
y puede tener referencias a otros archivos para configurar tipos de datos (objetos) y ejemplos de datos.
Veamos un ejemplo completo asociando otros archivos por medio de !include
…
root
)#%RAML 1.0
title: myapi
types:
mytype: !include plus/dataType.raml
/posts:
get:
responses:
200:
body:
application/json:
example: !include plus/example.raml
400:
body:
application/json:
example:
{ "message": "Not found" }
post:
body:
application/json:
type: mytype
responses:
200:
body:
application/json:
type: mytype
400:
body:
application/json:
example:
{ "message": "Something wrong" }
/{id}:
get:
responses:
200:
body:
application/json:
type: mytype
400:
body:
application/json:
example:
{ "message": "Not found" }
delete:
responses:
200:
body:
application/json:
example:
{ "message": "Deleted" }
400:
body:
application/json:
example:
{ "message": "Something wrong" }
put:
body:
application/json:
type: mytype
responses:
200:
body:
application/json:
type: mytype
400:
body:
application/json:
example:
{ "message": "Not found" }
#%RAML 1.0 DataType
type: object
properties:
id: string
author: string
title: string
content: string
Para datos numéricos se usa
number
#%RAML 1.0 NamedExample
value:
-
"id": "1",
"title": "Art",
"author": "John",
"content": "",
-
"id": "2",
"title": "Music",
"author": "Jane",
"content": "",
Con la combinación de estos archivos tendremos una especificación sencilla de nuestra API. RAML es una alternativa a OpenAPI (Swagger) y que en lo personal prefiero RAML por su sencillez. Además, es posible convertir RAML a OpenAPI.
En la versión comercial, MuleSoft cuenta con su propio lenguaje de expresiones para transformar datos, denominado DataWeave. Se trata de un lenguaje funcional y de script orientado a resolver el mapeo de datos del payload
de manera sencilla y potente. Tiene algunas similitudes a Javascript pero no encuentras bucles for
(aunque en su lugar se usa map
).
Recomiendo acceder a la introducción oficial a DataWeave
Alternativamente, encuentras este tutorial o su respectivo video
Veamos un ejemplo:
%dw 2.0
output application/json
---
payload map ( payload01 , indexOfPayload01 ) -> {
id: payload01.id,
name: payload01.title
}
Aquí se procesa cada elemento del
payload
(conmap
) aplicando una concordancia entre los datos recibidos y los que se van a mostrar, por ejemplo,name
se obtiene a partir depayload01.title
. Eloutput
indica que se mostrará en formatojson
.
En el segundo ejemplo buscaremos filtrar los datos…
%dw 2.0
output application/json
---
payload.data.children
filter (not $.data.ups < 200 or $.data.downs > 0)
map ( child , indexOfChild ) -> {
id: child.data.id,
title: child.data.title,
author: child.data.author,
content: child.data.selftext
}
En este caso, se elige un elemento para ser filtrado (con
filter
) y se mapea cada dato (conmap
)
Nótese que las líneas superiores que se encuentran antes de los caracteres ---
indican la versión del lenguaje a usar y el tipo de salida (json
), allí mismo podrían definirse funciones y variables para preperar los datos. En la región bajo los caracteres ---
definimos la manera en que se transforman los datos, es decir, el tratamiento de la salida. Generalmente, los datos se encuentran en el payload
o vars
, también se pueden aplicar funciones. El signo $
puede ser usado en un ámbito para referirse al elemento procesado en una iteración.
Veamos un ejemplo en XML
…
%dw 2.0
output application/xml
---
data:
item: payload map ( payload01 , indexOfPayload01 ) -> {
id: payload01.id,
name: payload01.title
}
Nótese que en el
output
se indicaxml
(application/xml
según elmimeType
respectivo). Además, se inicia con un elmento raíz (ej.data
).
De modo semejante ajson
yxml
, podríamos retornar datos en formatocsv
(Common Separate Version).
Finalmente, si se hace un request
y se desea que el body
se envíe como json
, se usa también DataWeave (de lo contrario, se procesa como si se tratara de un formulario). Por ejemplo:
%dw 2.0
output application/json
---
{
"name": "John",
"age": 25
}
Para comprender el concepto del uso de base de datos, usaremos el motor MySQL (o MariaDB), el cual debe estar instalado debidamente con su servicio iniciado.
Si cuentas con sistema Windows existen herramientas como Laragon (u otras) que facilitan su instalación y gestión de modo gráfico.
En Linux se inicia el servicio de MySQL ejecutando:
sudo systemctl start mysqld
En macOS puede iniciarse ejecutando:sudo /usr/local/mysql/support-files/mysql.server start
Para acceder a la consola de MySQL desde Linux ejecutamos:
mysql -u root -p
En Debian se ejecuta:
sudo mysql
En macOS se ejecuta:/usr/local/mysql/bin/mysql -u root -p
Desde allí podemos crear nuestra base de datos, por ejemplo myapi
, incluso podemos agregar un usuario y establecer sus privelegios salvo que se desee simplificar el ejercicio utiliando el usuario root
(aunque es conveniente tener un usuario específico). Veamos un ejemplo de las sentencia a utilizar:
CREATE DATABASE IF NOT EXISTS myapi;
CREATE USER 'myapi'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON mytest.* TO 'myapi'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
CREATE TABLE posts (
id varchar,
author varchar,
title varchar,
content varchar,
constraint pk_posts primary key (id)
);
De este modo, tendríamos nuestra base de datos configurada y nuestra primera tabla (posts
). A continuación se ilustra el esqueleto de las sentencias SQL que usaremos posteriormente en Mule.
SELECT * FROM posts;
SELECT * FROM posts WHERE id = :id;
INSERT INTO posts (id, author, title, content)
VALUE (:id, :author, :title, :content);
DELETE FROM posts WHERE id = :id;
UPDATE posts
SET title = :title, content = :content
WHERE id = :id;
Además de bases de datos como Oracle y MySQL, podemos configurar alternativamente una base de datos embebida como lo es H2 Database usando un tipo de conexion denominado Generic Connection (aplicando el conector Database o creando un elemento global). Para ello se debe especificar el artefacto de Maven respectivo, así:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.210</version>
</dependency>
En la configuración del conector Database, debemos suministrar la url
, por ejemplo jdbc:h2:/home/mydb
. Además, Driver class name, que correspondería a org.h2.Driver
, el usuario y la clave si aplican.
Para representar esto en xml
tendriamos el siguiente contenido…
<db:config name="Database_Config" doc:name="Database Config"
doc:id="9fb0e9b2-2276-4f6a-b129-729a278543b8" >
<db:generic-connection url="jdbc:h2:/home/mydb" driverClassName="org.h2.Driver" user="sa" password="" />
</db:config>
Retomaremos el ejemplo inicial para hacer un proyecto combinando algunas características. Comenzaremo usando el conector Request
…
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd">
<http:listener-config name="HTTP_Listener_config"
doc:name="HTTP Listener config" doc:id="a2665ce3-9e1f-4a9a-b802-e8a531d1729a" >
<http:listener-connection host="0.0.0.0" port="8081" />
</http:listener-config>
<http:request-config name="HTTP_Request_configuration"
doc:name="HTTP Request configuration" doc:id="6a110cd6-efe6-47c8-97bd-d8a57dd2007c" >
<http:request-connection protocol="HTTPS" host="www.reddit.com" />
</http:request-config>
<flow name="mytestFlow" doc:id="6da5d990-6097-4bb1-9df7-709503c8b3fd" >
<http:listener doc:name="Listener"
doc:id="3b8a0a1f-482c-47f2-a504-2436de5037bb" config-ref="HTTP_Listener_config" path="/load"/>
<http:request method="GET" doc:name="Request"
doc:id="a68732da-0663-454f-86e5-38ed6b7a9c51" config-ref="HTTP_Request_configuration" path="/r/aww.json"/>
</flow>
</mule>
En el siguiente paso se complementa el anterior y se busca representar el uso de Transform Message
. Veamos como queda el flow
…
<flow name="mytestFlow" doc:id="6da5d990-6097-4bb1-9df7-709503c8b3fd" >
<http:listener doc:name="Listener"
doc:id="3b8a0a1f-482c-47f2-a504-2436de5037bb" config-ref="HTTP_Listener_config" path="/load"/>
<http:request method="GET" doc:name="Request"
doc:id="a68732da-0663-454f-86e5-38ed6b7a9c51" config-ref="HTTP_Request_configuration" path="/r/aww.json"/>
<ee:transform doc:name="Transform Message"
doc:id="6f9b945a-ba68-4f29-8b21-f08f5dbea08d" >
<ee:message >
<ee:set-payload ><![CDATA[%dw 2.0
output application/json
---
payload.data.children map ( child , indexOfChild ) -> {
id: child.data.id,
title: child.data.title,
author: child.data.author,
content: child.data.selftext,
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</flow>
Agregemos ahora una conexión a la base de datos, lo cual quedaría representado como el siguiente contenido:
<db:config name="Database_Config"
doc:name="Database Config" doc:id="18df2f9d-6743-462b-a691-65a71e80f47d" >
<db:my-sql-connection host="localhost" port="3306" user="root" password="password" />
</db:config>
Y dentro de flow
tendríamos un conector de base de datos (Database
), por ejemplo así:
<db:bulk-insert doc:name="Bulk insert"
doc:id="8337459c-84c5-4a01-9de2-ffb3ed911589" config-ref="Database_Config">
<db:sql ><![CDATA[insert ignore into posts (id, author, content)
values (:id, :author, :title, :content);]]></db:sql>
</db:bulk-insert>
Para completar lo anterior, debemos aplicar otra transformación (Trannsform Message
), por ejemplo:
<ee:transform doc:name="Transform Message"
doc:id="36f5cac9-54eb-4c50-9663-5f96c1a6777e" >
<ee:message >
<ee:set-payload ><![CDATA[%dw 2.0
output application/json
---
{
success: true,
message: "OK",
total: sum(payload)
}]]></ee:set-payload>
</ee:message>
</ee:transform>
Ahora podemos consultar en el navegador en la ruta respectiva:
localhost:8080/load
Podríamos cambiar el Listener
inicial por Scheduler
para lograr una tarea programada. Es decir, cada cierto tiempo (1 hora) se consulta una API para cargar y sincronizar la información en una tabla de la base de datos. Su representación sería la siguiente:
Scheduler
~>
Request
~>
Transform Message
~>
Bulk Insert
~>
Transform Message
<scheduler doc:name="Scheduler"
doc:id="57883d72-b14b-4de3-a04b-deffad51574c" >
<scheduling-strategy >
<fixed-frequency frequency="1" timeUnit="HOURS"/>
</scheduling-strategy>
</scheduler>
A continuación encontraremos algunos trozos del archivo xml
generado por la herramienta Anypoint Studio para representar un ejercicio que consiste en una API de posts de un Blog. Es posible agilizar nuestro ejercicio ya que contamos con la especificación en RAML (que es lo primero que debe hacerse en este caso).
Debe seleccionarse el proyecto y abrir el menú desplegable, se busca la opción Manage Dependencies
. En la ventana que aparece se da clic en el icono del signo más y luego elegimos la opción from Exchange
. En la nueva ventana se filtra con el nombre asignado a la especificación. En caso de que no aparezca es posible que se haya definido el proyecto como privado y debas conectarte. Se seleccióna la línea que corresponde, se agrega el módulo y se finaliza la operación.
Se ilustra a continuación el esquema que se genera automaticamente a partir de la especificación…
Listener
~>
APIKit Router
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:apikit="http://www.mulesoft.org/schema/mule/mule-apikit" xmlns:db="http://www.mulesoft.org/schema/mule/db" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd http://www.mulesoft.org/schema/mule/mule-apikit http://www.mulesoft.org/schema/mule/mule-apikit/current/mule-apikit.xsd http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd http://www.mulesoft.org/schema/mule/db http://www.mulesoft.org/schema/mule/db/current/mule-db.xsd">
<http:listener-config name="myapi-httpListenerConfig">
<http:listener-connection host="0.0.0.0" port="8081" />
</http:listener-config>
<apikit:config name="myapi-config"
api="resource::f7bc2de9-104f-46b7-a071-9f2829f6fd08:myapi:1.0.2:raml:zip:myapi.raml"
outboundHeadersMapName="outboundHeaders" httpStatusVarName="httpStatus" />
</mule>
Si se trata de un proyecto local, la propiedad
api
deapikit:config
podría ser, por ejemplo,myapi.raml
Podríamos agregar una variable dentro de este flow
para determinar algún criterio, por ejemplo con la siguiente representación:
<set-variable value="json"
doc:name="Set Variable" doc:id="f5a2e314-04ff-4593-abfc-90c65decbd3f" variableName="kind"/>
Adicionalmente, encontraremos flujos privados para la API de consulta con el siguiente esquema…
Select
~>
Transform Message
<flow name="get:\posts:myapi-config">
<db:select doc:name="Select"
doc:id="2c3ae8a9-0161-4b44-a847-123919e36280" config-ref="Database_Config">
<db:sql ><![CDATA[select * from posts;]]></db:sql>
</db:select>
<ee:transform doc:name="Transform Message"
doc:id="2e3ce23f-7af8-497b-b2e1-338ec8dfc836" >
<ee:message >
<ee:set-payload ><![CDATA[%dw 2.0
output application/json
---
payload map ( payload01 , indexOfPayload01 ) -> {
id: payload01.id,
name: payload01.name
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</flow>
Suponiendo que agregamos la ruta apixml
para indicar que el resultado será en formato xml
(duplicando el flujo que incluye ÀPIKit Router
y asignando en ese flujo el valor de la variable kind
en xml
), tendríamos un flujo de consulta con el conector Choice
de la siguiente manera:
<flow name="get:\posts:myapi-config">
<db:select doc:name="Select"
doc:id="2c3ae8a9-0161-4b44-a847-123919e36280" config-ref="Database_Config">
<db:sql ><![CDATA[select * from posts;]]></db:sql>
</db:select>
<choice doc:name="Choice"
doc:id="456bd214-47ba-4cfc-86f0-49fd89eda88c" >
<when expression='#[vars.kind == "json"]'>
<ee:transform doc:name="Transform Message"
doc:id="2e3ce23f-7af8-497b-b2e1-338ec8dfc836">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
payload map ( payload01 , indexOfPayload01 ) -> {
id: payload01.id,
name: payload01.title
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</when>
<otherwise >
<ee:transform doc:name="Transform Message"
doc:id="b241159b-694d-4c1c-95ab-833fd14a4b37" >
<ee:message >
<ee:set-payload ><![CDATA[%dw 2.0
output application/xml
---
data:
item: payload map ( payload01 , indexOfPayload01 ) -> {
id: payload01.id,
name: payload01.title
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</otherwise>
</choice>
</flow>
Así podríamos representar las demás acciones de la API.
Con el connector extra para Java
, es posible invocar un método de una clase de este lenguaje. Por ejemplo, planteemos el siguiente código:
package mypackage;
public class MyComponent {
public static String getResult(String name) {
return "Hello " + name + " from Java!";
}
}
Teniéndo el código anterior, pensemos en un flujo como el siguiente:
Listener
~>
Set Variable
~>
Invoke static (Java)
~>
Set Payload
Para representar esto tendriamos el siguiente contenido…
<flow name="mysyncFlow1" doc:id="7328424d-904f-436b-a3bf-4faf7f55e8bf" >
<http:listener doc:name="Listener"
doc:id="37c0330b-9304-4d6d-9d16-7db563efd22d" config-ref="myapi-httpListenerConfig" path="/java"/>
<set-variable value="Andrey" doc:name="Set Variable"
doc:id="9f580932-d67a-4070-89c0-2c1848676abe" variableName="mytext"/>
<java:invoke-static method="getResult(java.lang.String)"
doc:name="Invoke static" doc:id="f2b67bc1-17ac-4692-98ea-a74789e13a27" class="mypackage.MyComponent">
<java:args ><![CDATA[#[arg0: vars.mytext]]]></java:args>
</java:invoke-static>
<set-payload value="#[payload]" doc:name="Set Payload"
doc:id="6dd4f299-aee3-4a4e-87ef-49e4764ae863" mimeType="text/plain"/>
</flow>
Se puede observar que se realiza una operación de transformación con el conector
Java
.
Además de algún requisito particular de usar códigoJava
, se puede ver como una alternativa cuando no cuentas con la versión empresarial de MuleSoft debido a que la versión comunitaria no incluyeDataWeave
.
Jython es la implementación de Python 2.7 para la JVM y también es posible usarlo en Mule gracias al conector Scripting
. Lo primero que hacemos es incorporar la configuración de la librería respectiva en Maven a través del archivo pom.xml
de nuestro proyecto, es decir, copiar y pegar el siguiente bloque dentro de las dependencias (<dependencies>
):
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-slim</artifactId>
<version>2.7.2</version>
</dependency>
Asegúrese de encontrar
groupid
yartifactId
también bajo la etiqueta<sharedLibraries>
delpom.xml
. Por ejemplo:
<sharedLibrary>
<groupId>org.python</groupId>
<artifactId>jython-slim</artifactId>
</sharedLibrary>
Pensemos que el script tendrá el siguiente contenido:
result = "Hi " + arg0 + " from Jython!"
Ahora pensemos en un flujo como el siguiente:
Listener
~>
Set Variable
~>
Scripting
~>
Set Payload
Para representar esto tendriamos el siguiente contenido…
<flow name="mysyncFlow2" doc:id="7f5b31f1-96b4-417a-bc95-003c948aef00" >
<http:listener doc:name="Listener"
doc:id="b7337c7e-9bb3-4e98-8de0-2ef7e1eaf1f4" config-ref="myapi-httpListenerConfig" path="/jython"/>
<set-variable value="Andrey" doc:name="Set Variable"
doc:id="0d25efb2-d7cf-401a-9362-8df11cd87813" variableName="mytext"/>
<scripting:execute engine="python" doc:name="Execute"
doc:id="db72e411-c730-4e49-bd90-fbb6c007c6c9">
<scripting:code><![CDATA[result = "Hi " + arg0 + " from Jython!"]]></scripting:code>
<scripting:parameters><![CDATA[#[arg0: vars.mytext]]]></scripting:parameters>
</scripting:execute>
<set-payload value="#[payload]" doc:name="Set Payload"
doc:id="cf89d67d-3f6c-44e8-98df-0474bc6f4e03" mimeType="text/plain"/>
</flow>
Se puede observar que se realiza una operación de transformación con el conector
Scripting
(scripting:code>
).
Esto también se puede ver como una alternativa cuando no cuentas con la versión empresarial de MuleSoft debido a que la versión comunitaria no incluyeDataWeave
.
LuaJ es la implementación de Lua 5.2 para la JVM y es también posible usarlo en Mule gracias al conector Scripting
.
Basados en lo visto anteriormente para Jython, veamos el escenario para LuaJ, empezando con el archivo pom.xml
.
<dependency>
<groupId>org.luaj</groupId>
<artifactId>luaj-jse</artifactId>
<version>3.0.1</version>
</dependency>
Asegúrese de encontrar
groupid
yartifactId
también bajo la etiqueta<sharedLibraries>
delpom.xml
.
Pensemos que el script tendrá el siguiente contenido:
result = "Hi " .. arg0 .. " from LuaJ!"
Y la etiqueta xml
del Mule podría verse así:
<scripting:execute engine="lua" doc:name="Execute"
doc:id="db72e411-c730-4e49-bd90-fbb6c007c6c9">
<scripting:code><![CDATA[result = "Hi " .. arg0 .. " from LuaJ!"]]></scripting:code>
<scripting:parameters><![CDATA[#[arg0: vars.mytext]]]></scripting:parameters>
</scripting:execute>
JRuby es la implementación de Ruby 2.6 para la JVM y es también posible usarlo en Mule gracias al conector Scripting
.
Basados en lo visto anteriormente para Jython, veamos el escenario para JRuby, empezando con el archivo pom.xml
.
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-core</artifactId>
<version>9.3.3.0</version>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-stdlib</artifactId>
<version>9.3.3.0</version>
</dependency>
Asegúrese de encontrar
groupid
yartifactId
también bajo la etiqueta<sharedLibraries>
delpom.xml
.
Pensemos que el script tendrá el siguiente contenido:
"Hi from JRuby!"
Y la etiqueta xml
del Mule podría verse así:
<scripting:execute engine="ruby" doc:name="Execute"
doc:id="db72e411-c730-4e49-bd90-fbb6c007c6c9">
<scripting:code><![CDATA["Hi from JRuby!"]]></scripting:code>
<scripting:parameters><![CDATA[#[arg0: vars.mytext]]]></scripting:parameters>
</scripting:execute>
Un camino muy estrecho
Dado que Kotlin proporciona interoperabilidad con Java, podría llegar a pensarse en usar este lenguaje con MuleSoft. Un ejemplo potencial del código anteriormente visto para Java podría ser el siguiente:
package mypackage;
class MyKotlinComponent {
companion object {
@JvmStatic
fun getResult(name: String): String {
return "Hello " + name + " from Kotlin!";
}
}
}
Sin embargo, actualmente con Mule 4 al configurar Maven no es posible integrar directamente el proceso de compilación para Kotlin. Por ello, lo que se debe hacer es un proyecto independiente de Mule y, mediante una clase de Java, importar las clases de una librería .jar
. Las librerías se agregan danndo clic derecho sobre el elemento de nuestro proyecto (dentro del Package Explorer
) para desplegar un menú, allí elegimos la opción Build Path
y luego Add External Archives
.
La otra alternativa consistiría en usar Anypoint Studio para crear un projecto bajo Kotlin que funcione como dependencia de nuestro proyecto en Mule.
Anypoint Studio se basa en Eclipse pero no viene con la opción de Marketplace, por lo que debemos agregar primero tal opción y luego el complemento para Kotlin. El Marketplace puede habilitarse mediante el menú Help / Install new software
, agregando el repositorio Oxygen
con el enlace https://download.eclipse.org/releases/oxygen
. Cuando nos aparezca la lista, dentro del software disponible, buscamos la opción General Purpose Tools / Marketplace Client
.
Una vez instalado el Marketpalce, reiniciamos Anypoint Studio y encontraremos la opción respectiva en el menu Help
. De este modo, podremos buscar el complemento para Kotlin e instalarlo (y reniciamos la herramienta de nuevo).
Si se resuelve seguir este camino, debe recordarse que, por compatibilidad con el JDK de Anypoint Studio, en el archivo de configuración del proyecto (ej.
pom.xml
) se usaría la versión de JDK 8,
Caundo deseamos ejecutar una instancia local de Mule, lo primero que debemos hacer es descargar la versión del Runtime (o Kernel) desde el sitio oficial
Una vez descargado simplemente se descomprime y se deja en una ubicación deseable. La carpeta descargada suele tener una estrucutra como la siguiente:
De modo esencial, podemos tener presente que en
bin
se encuentra el ejecutable y enapps
se ubican los despliegues.
Para iniciar el servicio nos ubicamos en bin
y podemos ejecutar, por ejemplo:
./mule console
./mule start
./mule stop
Mule utiliza Java Service Wrapper para su instalación y lanzamiento, en caso de inconvenientes con dicho componente encuentras en Internet la version apropiada para tu sistema (por ejemplo:
wrapper-macosx-universal-64
).
Se debe tener presente que una aplicación se despliega en principio usando Anypoint Studio, por lo que debe accedcer a la herramienta y utilizar del menú File
la opción Export
. Allí se abre la carpeta o categoría Mule
y encuentras la opción Anypoint Studio Project to Mule Deployable Archive
. Luego podemos seleccionar el proyecto. Esto nos genera una archivo .jar
que se ubicaría en la carpeta apps
de nuestra instalación de Mule. De hecho, podemos indicarlo esta ruta en la generación del .jar
.
También es poisble usar la plataforma en la nube para configurar con MuleSoft Runtime servidores Standalone y gestionar su despliegue.
Una alternativa al uso de Anypoint Studio es usar Maven (y su archivo pom.xml
). De esta manera podríamos configurar nuestro proyecto y desplegarlo como vimos en un punto anterior. Evidentemente, no tendremos un entorno visual por lo que tendremos que usar Mule con archivos xml
y quizás un poco de Java.
Sin embargo, la última versión disponible como artefacto de Maven, para construir un proyecto de Mule, No es basada en la 4 sino en la 3 (
3.9.0
) cuya actualización se lanzó el 17 de febrero de 2021 usando Java versión8
.
Basados en lo anterior, como requisito debe estar instalado Maven y debidamente configurado. Maven suele configurarse en la ruta $HOME/.m2
. En .m2
puede establecerse un archivo de configuración con nombre settings.xml
y con un contenido como el siguiente:
<settings>
<pluginGroups>
<pluginGroup>org.mule.tools</pluginGroup>
</pluginGroups>
<profile>
<id>Mule Org</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>mulesoft-releases</id>
<name>MuleSoft Repository</name>
<url>https://repository-master.mulesoft.org/releases/</url>
<layout>default</layout>
</repository>
<repository>
<id>mulesoft-snapshots</id>
<name>MuleSoft Snapshot Repository</name>
<url>https://repository-master.mulesoft.org/snapshots/</url>
<layout>default</layout>
</repository>
</repositories>
</profile>
</settings>
Si ya exsite un archivo
.m2/settings.xml
, debe interpretarse loa anterior de modo que se copie el contenido necesario dentro del bloque respectivos (ejemplo:profile
).
Para iniciar el proyecto usando Maven, nos ubicamos en una carpeta destinada como espacio de trabajo para proyectos de código de software, y ejecutamos el comando a continuación:
mvn mule-project-archetype:create -DartifactId=myproject -DmuleVersion=3.9.0
myproject
corresponde al nombre de la nueva carpeta para nuestro proyecto.3.9.0
es la última versión admitida usando Maven.
Si se ejecuta bien nos presentará unas preguntas sobre la descripcion, la versión (3.9.0
), el package
del proyecto representado expresado como un path
(es decir, con /
), los transportadores (por defecto: file,http,jdbc,jms,vm
), y los modulos que se deseen incluir (por ejemplo, json,client,cxf,management,scripting,sxc,xml
), los cuales se pueden dejar por defecto. Finalmente, obtendremos una nueva carpeta correspondiente a nuestro proyecto.
Para implementar nuestra aplicación usaremos el archivo src/main/app/mule-config.xml
, es decir, lo abrimos y colocamos un nuevo flujo con un contenido como el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd">
<flow name="myMuleFlow">
<http:inbound-endpoint exchange-pattern="request-response"
host="localhost" port="8082" />
<set-payload value="Hi there!"/>
</flow>
</mule>
Después de guardar y cerrar el archivo, podemos ejecutar el comando de Maven siguiente:
mvn package -DskipTests
Para el desplieuge ejecutaríamos un comando como el siguiente:
mvn package deploy -DmuleDeploy -Dmule.version=3.9.0 -Dmule.home=$HOME/mule-standalone-3.9.0 -DskipTests
Lo que se busca es reemplazar el archivo mule-config.xml
en la ruta apps/default
de Mule 3.9.0. Si lo anterior no funciona, es preciso revisar los conceptos asociados a las carpetas para establecer un despliegue manual. Por ejemplo, crear la carpeta del proyecto bajo la carpeta apps
y mover nuestro archivo xml
, aunque quizás se requiera configurar un dominio (domains
).