Mostrando entradas con la etiqueta Enterprise Integrator. Mostrar todas las entradas
Mostrando entradas con la etiqueta Enterprise Integrator. Mostrar todas las entradas

lunes, 11 de enero de 2021

WSO2 Mediator Tutorial: Como usar la cache

Hoy vamos a volver a post sobre WSO2 y sus mediadores. Veremos como funciona el Cache Mediator, el cual puede ser muy util aunque no es muy conocido. 

La idea es sencilla, a través de este mediador, WSO2 cacheará la respuesta de un backend determinado en base a la petición que vayamos a hacer. ¿Como lo hace? Obtiene un valor hash asociado al mensaje almacenado en el flujo. Si existe ejecutará la secuencia configurada en el parámetro onCacheHit, el cual es opcional, obtiene la respuesta almacenada en el cache y envía la respuesta. Si no existe, se realiza la llamada al backend y se almacena la respuesta para su posible uso futuro. 

Vamos a ver un ejemplo sencillo de su uso. Vamos a crear un recurso de una API que llame a un backend, hecho con Wiremock, y antes del cual configuraremos el Cache Mediator para su uso. En el mediador configuraremos varias cosas aparte de las que vienen por defecto. Como son que la cache se mantenga durante 20 segundos, que solo cachee las llamadas de métodos POST y que como mucho almacene 10 elementos en la cache. 

<resource methods="POST" url-mapping="/sample1">
    <inSequence>
        <property name="REST_URL_POSTFIX" scope="axis2" action="remove"/>
        <cache collector="false" timeout="20">
            <onCacheHit>
                <log level="custom">
                    <property name="testing" value="Cache Hit"/>
                </log>
                <respond/>
            </onCacheHit>
            <protocol type="HTTP">
                <methods>POST</methods>
                <headersToExcludeInHash/>
                <responseCodes>.*</responseCodes>
                <enableCacheControl>false</enableCacheControl>
                <includeAgeHeader>false</includeAgeHeader>
                <hashGenerator>org.wso2.carbon.mediator.cache.digest.DOMHASHGenerator</hashGenerator>
            </protocol>
            <implementation maxSize="10"/>
        </cache>
        <send>
            <endpoint>
                <http method="POST" uri-template="http://localhost:57001/sample1"/>
            </endpoint>
        </send>
    </inSequence>
    <outSequence>
        <cache collector="true"/>
        <respond/>
    </outSequence>
</resource>

A continuación realizaremos una invocación a la API y la primera vez veremos como el Wiremock es invocado, y cuando volvemos a llamar una segunda vez no. 

curl --location --request POST 'http://localhost:8280/cache/sample1' \
--header 'Content-Type: application/json' \
--data-raw '{}'

Como veis en el ejemplo, el uso de cache mediator se divide en dos partes. Por un lado la configuración de la propia cache antes de llamar al backend y por otro lado una nueva invocación a la misma pero para indicar que nos devuelva el valor asociado a la misma si es que hay. En esta segunda invocación no es necesario configurar nada, solo indicar a true el parámetro collector

El cache mediator también puede ser utilizado para llamadas al backend a través del call mediator. En el caso de que decidamos que queremos usarlo así, tendremos que indicar a Synapse despues de la llamada al backend, que nos encontramos tratando con un mensaje de respuesta. Esto lo haremos a través de la propiedad RESPONSE. Sin esa propiedad, en este ejemplo, el cache mediator no funcionará. 

<resource methods="POST" url-mapping="/sample2">
    <inSequence>
        <property name="REST_URL_POSTFIX" scope="axis2" action="remove"/>
        <cache collector="false" timeout="20">
            <protocol type="HTTP">
                <methods>POST</methods>
                <headersToExcludeInHash/>
                <responseCodes>.*</responseCodes>
                <enableCacheControl>false</enableCacheControl>
                <includeAgeHeader>false</includeAgeHeader>
                <hashGenerator>org.wso2.carbon.mediator.cache.digest.DOMHASHGenerator</hashGenerator>
            </protocol>
            <implementation maxSize="10"/>
        </cache>
        <call>
            <endpoint>
                <http method="POST" uri-template="http://localhost:57001/sample1"/>
            </endpoint>
        </call>
        <property name="RESPONSE" value="true" scope="default" type="STRING"/>
        <cache collector="true"/>
        <respond/>
    </inSequence>
    <outSequence />
</resource>

Como veis es muy sencillo de usar y no requiere de apenas configuración. Por ponerle alguna pega, podriamos decir que no cuenta de un método que permita borrar la cache para un determinado mensaje, por lo que en caso de que la respuesta no valiese ya, solo podría ser cambiada una vez que se dicha cache se caducase. 

miércoles, 20 de mayo de 2020

WSO2 Data services: Generación automática de servicios CRUD

Ya estuvimos aquí haciendo un post sobre como hacer servicios REST con WSO2. Hoy veremos los OData. O lo que es lo mismo, la generación automática de estos web services. OData hace referencia a Open Data Protocol, un standard de OASIS. Este estándar define las buenas practicas para construir y consumir una API RESTful. Es algo bastante sencillo de realizar pero creo que es una característica de WSO2 que no mucha gente conoce. 

Para poder generar automaticamente estos web services, debemos seguir los siguientes pasos desde la consola de administración:
  • Crear un nuevo data service
  • Asociar un datasource, con la diferencia de que debemos marcar la opción de habilitar OData.
  • Salvar el data source y dar por terminada la edición del data service. 
Si vemos el código podemos verificar que es un data services muy sencillo:

<data name="ODataSample" transports="http https local">
   <config enableOData="true" id="ODataDS">
      <property name="driverClassName">com.mysql.jdbc.Driver</property>
      <property name="url">jdbc:mysql://localhost:3306/library_db</property>
      <property name="username">root</property>
      <property name="password">****</property>
   </config>
</data>

Solo con esto WSO2, nos generará un servicio CRUD por cada tabla del data source que hemos indicado. Esto es un punto a tener en cuenta, puesto que si el data source contiene tablas del sistema, fallará la generación del data service. La capacidad de realizar CRUD de vistas de BBDD solo es posible si aplicamos un WUM sobre el Enterprise Integrator. 

Una vez generado, veamos como podemos obtener todos los datos de la tabla BOOK

curl --location --request GET 'https://localhost:8243/odata/ODataSample/ODataDS/BOOK' \
--header 'Accept: application/json'

Si vemos la llamada debemos estar atentos a varios detalles:
  • El puerto para llamadas HTTP sigue siendo 8280 y 8243 para HTTPS
  • El contexto para invocar estos servicios es odata y no services
  • Seguimos necesitando poner el nombre de nuestro data service. En este caso es ODataSample.
  • Después del nombre del data service debemos indicar el nombre del data source que hemos configurado. En este caso ODataDS.
  • Por último debemos indicar el nombre de la tabla. En nuestro ejemplo, BOOK. 
También asociado al método GET podemos realizar las siguientes operaciones:
  • Si no indicamos ninguna tabla. Nos mostrarán la información de las distintas tablas existentes
  • Si no indicamos ninguna tabla y añadimos al path /$metadata e invocamos como application/xml, nos devolverá la información del formato de todas las tablas del data source. 
  • Si queremos obtener sólo determinados campos de una tabla debemos añadir ?$select=fieldName, con el nombre de los campos a obtener separados por comas. Ejemplo: 
curl --location --request GET 'https://localhost:8243/odata/ODataSample/ODataDS/BOOK?$select=NAME,AUTHOR' \
--header 'Accept: application/json'
  • Si queremos un filtrado de los datos de la tabla añadiremos al path ?$filter=fieldName eq 'fieldValue'. Ejemplo:
curl --location --request GET 'https://localhost:8243/odata/ODataSample/ODataDS/BOOK?$filter=NAME%20eq%20%27Hyperion%27' \
--header 'Accept: application/json'

Si queremos borrar un registro realizaremos la siguiente llamada

curl --location --request DELETE 'https://localhost:8243/odata/ODataSample/ODataDS/BOOK(26)'

Para crear un registro debemos utilizar como es normal el método POST e indicar en el cuerpo de la llamada los campos a introducir

curl --location --request POST 'https://localhost:8243/odata/ODataSample/ODataDS/BOOK' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Content-Type: application/json' \
--data-raw '{
	"NAME":"Ender's game",
	"AUTHOR": "Orson S. Card"
}'

Y por último para realizar un update, utilizaremos el método PUT indicando aquellos los cuales queremos editar

curl --location --request PUT 'https://localhost:8243/odata/ODataSample/ODataDS/BOOK(28)' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Content-Type: application/json' \
--data-raw '{
	"NAME":"Juego de Ender"
}'



viernes, 7 de febrero de 2020

WSO2 EI: Como crear un servicio con acceso a BBDD

Hoy vamos a ver algo muy facil de hacer y muy util. Vamos ver como crear un data service, asociarle un data source y crear una operación asociada. 

Los datasources son los elementos de configuración que nos permiten conectarnos con diferentes origenes de datos. Desde base de datos tradicionales, a hojas de excel o google sheet, CSV o base de datos NoSQL como Cassandra o MongoDB. 

Otra cosa que debemos saber de estos Datasources, es que podemos configurarlos de distintas formas:
  • Desde el fichero custom-datasources.xml
  • Desde la consola de administración. Accediendo a la pestaña Configure y la  opción de menú Datasources.
  • Desde el propio Data service.  
Los data services serán aquellos servicios que teniendo como origen esos datasources, expondrán mediante operaciones o recursos los datos del mismo. Para exponer esos datos deberremos realizar los siguientes pasos:
  • Crear el data service e indicar los protocolos de acceso al mismo. 
  • Asociar al menos un datasource. 
  • Crear un consulta SQL.
  • Crear una operación o recurso asociado a la consulta. 
 Al crear una consulta debemos realizar tres pasos fundamentales:

  • Escribir la propia consulta SQL. 
  • Indicar los parámetros de entrada
  • Indicar los parámetros de salida y su formato. 
Tanto la creación de parámetros de entrada y salida se pueden crear de forma automática con los botones Generate Input Mappings y Generate Response. A ambos se les puede indicar el tipo de elemento (por defecto, String). En los parámetros de entrada se pueden incluir validaciones y respecto a la salida, por defecto será en formato XML y podemos indicarle el nombre de los objetos que envuelvan esos registros. 

El siguiente paso es la creación de la operación o recurso. En ambos casos es relativamente sencillo, solo debemos indicar la consulta que estará asociada a estos y de forma automática se creará el propio mapeo del propio recurso u operación. La diferencia principal, es que una operación habilitará un servicio de tipo SOAP para realizar la consulta asociada. Y si creamos un recurso, el servicio será de tipo REST. 


Si queremos probarlo, en el listado de servicios hay un botón denominado Try this service que abrirá una nueva ventana para realizar las pruebas. En el caso de las operaciones para poder invocarlo realizaremos la siguiente llamada:

curl --insecure \
--request POST 'https://localhost:8243/services/sampleDataservice.SecureSOAP12Endpoint' \
--header 'Content-Type: application/xml' \
--data-raw '<p:SELECT_OP xmlns:p="http://ws.wso2.org/dataservice">
      <xs:ID xmlns:xs="http://ws.wso2.org/dataservice">1</xs:ID>
   </p:SELECT_OP>'

La ruta del servicio estará compuesta por el nombre del servidor, el puerto (8280 para HTTP y 8243 para HTTPS, por defecto). De contexto /services/ y el nombre del data service, y por último el sufijo en función del tipo de endpoint SOAP que vayamos a usar.

El cuerpo del mensaje tendra como raiz el elemento con nombre de la operación (SELECT_OP en este caso) y compuesto por los distintos atributos de la consulta.
asdfad

lunes, 8 de julio de 2019

WSO2: Novedades Enterprise Integrator 6.5.0


Este pasado 23 de Mayo de 2019 se lanzo la versión 6.5.0 del Enterprise Integrator. Y aquí vamos a hacer una revisión de todas sus novedades. Principalmente las asociadas al EI, aunque también hay algunas asociadas al Integration Studio o al Micro Integrator que puedes ver aquí


Se ha creado un mediator que permita la declaración múltiple de propiedades dentro de la integración. Algo muy demandado por los desarrolladores y que evitará muchas lineas de código. 

<propertyGroup>
        <property expression="get-property('SYSTEM_DATE', 'dd-MM-yyyy')" name="systemDate"/>
        <property expression="get-property('Action')" name="action"/>
        <property expression="$axis2:ContentType" name="contentType"/>
    </propertyGroup>

  • JsonPath en los mediators Aggregate, Iterate y Enrich
Además de poder utilizar XMLPath en estos mediators, ahora tendremos la posibilidad de utilizar tambie'n JsonPath a través de json-eval.

<?xml version="1.0" encoding="UTF-8"?>
<sequence name="enrichSequence" xmlns="http://ws.apache.org/ns/synapse">
    <enrich>
        <source clone="false" type="custom"
            xmlns:ns="http://org.apache.synapse/xsd" xpath="json-eval($.name)"/>
        <target action="replace" property="bookName" type="property"/>
    </enrich>
    <respond/>
</sequence>
Los poison messages son aquellos mensajes que se han intentado mandar el limite permitido de veces a través de un Message Processor. Y cuando pasa esto, el Message Processor entra en estado desactivado. Hasta ahora se podían descartar aquellos poison messages, ahora además podremos reenviarlos a otro message store.
Para ello deberemos acceder a la opción de menú Home > Message Processors. En esta página debemos pulsar sobre el enlace View Message asociado al message processor que tenemos desactivo. Y una vez dentro iremos mensaje por mensaje decidiendo que queremos hacer: decartarlo, no hacer nada o la nueva opción, redirigirlo a otro message store que haya sido previamente registrado.
  • Mejora en el soporte REST para Data Services basados en JSON
Y además se habilita la característica Request Box para este tipo de servicios. Con la cual podemos realizar diferentes operaciones dentro de una única llamada. Esta nueva característica cuenta con las siguientes particularidades:
    • Debemos llamar al servicio indicando el recurso request_box y pasarle en el body una petición con un formato determinado
    • Este formato viene encabezado por el objeto request_box y cada uno de sus hijos es un objeto que comprende una operación, la cual a su vez comprende el conjunto de parámetros necesarios para llevarla a cabo.
    • Estas operaciones incluidas en un objeto tienen un formato de nombre concreto: _<Metodo HTTP><Recurso del DS>. Ej: _postemployee
  • Soporte de OData Services para MongoDB
  • Soporte para monitorización de estadísticas con Prometheus
  • Mejora en el rendimiento en alta escalabilidad de los datos analíticos
Como veis muchas novedades y además se ha deprecado otras tantas:

    • Priority Executors.
    • Mediators: Enqueue, Bean, POJO Command, Spriong Conditional router, Event e In y Out.
    • MSF4J Profile

sábado, 2 de febrero de 2019

WSO2 Mediator Tutorial: Como evitar incluir namespace con enrich

Ya vimos hace poco la diferencia del enrich y el payload como mediators en WSO2, aquí. Con Payload al sustituir el body teniamos un mayor control de la salida del mismo. Con enrich podemos modificar la respuesta remplazando un valor o añadiendo un hijo/hermano.

Partiendo del ejemplo anterior, podemos indicar que la respuesta recibida en formato XML a través del siguiente código:

<property name="messageType" value="text/xml" scope="axis2"/>

Y será el Enterprise Integrator el encargado de transformalo correctamente gracias a los Message formatter. Y por lo tanto la nueva salida de nuestro método será la siguiente:

<jsonObject>
    <bookstore>
        <name>the starts my destination</name>
        <author>Alfred Bester</author>
    </bookstore>
    <description xmlns="http://ws.apache.org/ns/synapse">list of books</description>
    <bookstore>
        <name>Ender's Game</name>
        <author>Orson S. Card</author>
    </bookstore>
    <total xmlns="http://ws.apache.org/ns/synapse">2</total>
</jsonObject>

Aparte de que al recibir originalmente la respuesta como un json y aunque transforma dicha respuesta a XML la incluye dentro del nodo 'jsonObject'. Podemos ver como aquellos nodos que hemos incluido como hermano o hijo se han creado con un namespace por defecto.

En algunos casos esto puede darnos igual, pero es posible que esta respuesta sea validada por otra entidad a través de un XSD o alguna otra cosa. Y por tanto dichos namespace nos podrian generar fallos.

Para evitar la inclusión del namespace hay una solución específica y es indicarle un namespace vacio, así:

<enrich>
    <source clone="false" type="inline">
        <total xmlns="">2</total>
    </source>
    <target action="child" type="body"/>
</enrich>
<enrich>
    <source clone="false" type="inline">
        <description xmlns="">list of books</description>
    </source>
    <target action="sibling" type="custom" xpath="//bookstore"/>
</enrich>

De la forma anterior, la nueva salida será igual pero sin namespaces asociados a los nodos incluidos:

<jsonObject>
    <bookstore>
        <name>the starts my destination</name>
        <author>Alfred Bester</author>
    </bookstore>
    <description>list of books</description>
    <bookstore>
        <name>Ender's Game</name>
        <author>Orson S. Card</author>
    </bookstore>
    <total>2</total>
</jsonObject>

sábado, 5 de enero de 2019

WSO2: Diferencia entre PayloadFactory y Enrich Mediator


Ultimamente estoy trabajando con WSO2 y me había estado resistendo a crear posts de algo tan específico. Pero creo que poco a poco vamos a hacer ejemplos básicos de esta técnologia de integración. 

Para empezar vamos a realizar un post sobre dos de los mediators más comunes que podemos utilizar dentro de una sequencia del Enterprise Integrator. Como son 'PayloadFactory' y 'Enrich'.  El primero de ellos nos permitirá modificar el cuerpo de la respuesta y crear uno nuevo a nuestra conveniencia. El segundo nos permitirá modificar el cuerpo de la respuesta pero solo añadiendole nuevos valores. 

Para el ejemplo partiremos en ambos casos de una API de prueba. Donde en la sequencia de entrada llamaremos a un wiremock que nos devolverá la respuesta mostrada a continuación. Y será esta respuesta la que va a ser modificada por los dos mediators:

{"bookstore": [
      {"name": "the starts my destination", "author": "Alfred Bester"},
      {"name": "Ender's Game", "author": "Orson Scott Card"}
]}

Con el 'PayloadFactory' mediator, vamos a indicar cual queremos que sea nuestra nueva respuesta. Nos permite incluso crear una nueva respuesta completamente estática, aunque no tenga mucho sentido. Para darle dinamismo tenemos los 'arguments', estos argumentos los indicaremos dentro del cuerpo de la respuesta como '$x', siendo 'x' el ordinal del mismo en la lista global de argumentos. Pueden ser obtenidos de 4 formas: a través de XPath o JSONPath sobre el cuerpo de la actual respuesta, mediante valores literales o con propiedades ya almacenadas en cualquiera de los ambitos dentro del EI. A continuación veremos un ejemplo con JSONPath, literales y valores del contexto:

<payloadFactory media-type="json">
        <format>
{"books":[{"book":[{"name":"$1","author":"$3"}]},{"book":[{"name":"$2","author":"$4"}]}],"total":"$5","description":"$6"}
            </format>
        <args>
            <arg evaluator="json" expression="$.bookstore[0].name" literal="true"/>
            <arg evaluator="json" expression="$.bookstore[1].name" literal="false"/>
            <arg evaluator="json" expression="$.bookstore[0].author" literal="false"/>
            <arg evaluator="json" expression="$.bookstore[1].author" literal="false"/>
            <arg literal="false" value="2"/>
            <arg evaluator="xml" expression="$ctx:description" literal="false"/>
        </args>
    </payloadFactory>

Generando la siguiente salida:

{"books":[
  {"book":[{"name":"the starts my destination","author":"Alfred Bester"}]},
  {"book":[{"name":"Ender's Game","author":"Orson Scott Card"}]}],
  "total":"2","description":"list of books"}

Ahora veremos como funciona el 'Enrich' mediator. Con el basicamente le indicaremos una fuente y un destino, pudiendo realizar 3 tipos de acciones diferentes: añadir como hijo, añadir como hermano o reemplazar. La configuración para la fuente y el destino son similares, se pueden elegir que sean de 4 tipos: personalizada en base a un XPath, envelope del mensaje original, el cuerpo del mensaje original o una propiedad de un ambito. Aunque existe una matrix de compatibilidades para los distintos tipos de fuentes y destinos, por lo que no vale cualquier combinación. Aquí vemos un ejemplo donde añadimos un hijo, un hermano y reemplazamos un valor.

<enrich>
    <source clone="false" type="inline">
        <total>2</total>
    </source>
    <target action="child" type="body"/>
</enrich>
<enrich>
    <source clone="false" type="inline">
        <description>list of books</description>
    </source>
    <target action="sibling" type="custom" xpath="//bookstore"/>
</enrich>
<property name="shortName" scope="default" type="STRING" value="Orson S. Card"/>
<enrich>
    <source clone="false" property="shortName" type="property"/>    <target action="replace" type="custom"
            xmlns:ns="http://org.apache.synapse/xsd" xpath="//bookstore[2]/author"/>
</enrich>

La salida en este caso sería la siguiente:

{"bookstore": { "name": "Ender's Game", "author": "Orson S. Card" }
,"description": "list of books", "total": 2}

Por ulitmo, con enrich una utilidad común es guardar la respuesta original, aunque también lo podemos hacer con el 'Property' mediator. Y luego restablecer dicha respuesta original. Muy util para cuando tienes que hacer llamadas intermedias en tu sequencia.

<property name="REQUEST_PAYLOAD" expression="$body" />
<!-- more code -->
<enrich>
    <source clone="true" xpath="$ctx:REQUEST_PAYLOAD" />    <target type="body" />
 </enrich>