jueves, 14 de abril de 2016

WSO2 ESB. Orquestando APIs con Iterate/Aggregate

Revisando en el sitio de stackoverflow me he topado con una duda ya respondida y aproveché para hacerle algunos cambios y adaptarla a un demo de uso de APIs en el WSO2 ESB que contempla lo siguiente:
  1. Se tienen 2 APIs que tienen cableadas las respuestas en JSON. Esos serían nuestros backend.
  2. Se tiene además otra API que debe implementar la invocación a la primera API que devuelve el listado de personas, luego debe iterar por cada persona para obtener un ID y con ese ID consultar a la segunda API. Dentro de cada iteración debe crear un nuevo mensaje que incluya tanto la respuesta de la primera API como de la segunda y retornarlo para que sea capturada en el flujo de salida. 
  3. La respuesta será entonces la unión de las respuestas de las 2 APIs generada a partir de la iteración y agregación de los mensajes finales.
Veamos como se hace:

Primero la imagen de la implementación en el WSO2 Developer Studio.
image

Lamentablemente WSO2 hizo su diseño gráfico hacia un costado y no hacia abajo como lo tiene Oracle así que es dificil fijar la imagen en una página, por lo que iremos presentando pedazos para que se vea bien.
Primero la definición de la API:

<?xml version="1.0" encoding="UTF-8"?>
<api context="/services/userdetails" name="UserDetailsAPI" xmlns="http://ws.apache.org/ns/synapse">
  <resource methods="GET" protocol="http">
    <inSequence>
    ....
    </inSequence>
    <outSequence>
    ....
    </outSequence>
    <faultSequence>
 ....
    </faultSequence> 
  </resource>
</api>  

Dentro de la secuencia de entrada las acciones son las siguientes:

Invocar al API que devuelve el listado de personas:
image 

<call description="invocacion al BE para obtener las personas">
<endpoint>
  <http method="get" trace="disable" uri-template="http://localhost:8281/services/users"/>
</endpoint>
</call>

Para ello usamos el mediador call e invocamos al API correspondiente usando el método GET.
La respuesta de esta invocación será la siguiente:
{
        "persons":
        [
            {
                "person":
                {
                    "Id": "1",
                    "givenName": "ajith",
                    "lastName": "vitharana",
                    "age": "25",
                    "contactInfos":
                    [
                        {
                            "InfoId": "1",
                            "department": "1",
                            "contactType": "email",
                            "value": "ajith@abc.org"
                        },
                        {
                            "InfoId": "2",
                            "department": "1",
                            "contactType": "mobile",
                            "value": "111111111"
                        },
                        {
                            "InfoId": "3",
                            "department": "1",
                            "contactType": "home",
                            "value": "Magic Dr,USA"
                        }
                    ]
                }
            },
            {
                "person":
                {
                    "Id": "2",
                    "givenName": "shammi",
                    "lastName": "jagasingha",
                    "age": "30",
                    "contactInfos":
                    [
                        {
                            "InfoId": "1",
                            "department": "1",
                            "contactType": "email",
                            "value": "shammi@abc.org"
                        },
                        {
                            "InfoId": "2",
                            "department": "1",
                            "contactType": "mobile",
                            "value": "2222222222"
                        },
                        {
                            "InfoId": "3",
                            "department": "1",
                            "contactType": "home",
                            "value": "Magic Dr,USA"
                        }
                    ]
                }
            }
        ]
    }


Iterar sobre el listado de personas devueltas:
image 

<iterate attachPath="//jsonObject/persons"
description="iterador sobre cada elemento person"
expression="//jsonObject/persons/person" id="it1"
preservePayload="true" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<target>
  <sequence>
 <property expression="$body/jsonObject/persons/person/Id"
   name="uri.var.Id" scope="default" type="STRING" xmlns:ns="http://org.apache.synapse/xsd"/>
 <property expression="$body//jsonObject//person"
   name="response1" scope="default" type="STRING" xmlns:ns="http://org.apache.synapse/xsd"/>
 <enrich>
   <source clone="true" xpath="$body//jsonObject//person"/>
   <target property="Person" type="property"/>
 </enrich>
 <call description="Se invoca al BE para obtener los roles usando el ID del elemento person correspondiente">
   <endpoint>
  <http method="get" trace="disable" uri-template="http://localhost:8281/services/roles/{uri.var.Id}"/>
   </endpoint>
 </call>
 <!--Aqui se enriquece el mensaje sobre el que se itera 
     para incluirle la respuesta de la call con los roles -->
 <enrich>
   <source clone="true" xpath="$body//jsonObject//roles"/>
   <target action="child" xpath="$ctx:Person"/>
 </enrich>
 <enrich>
   <source clone="true" xpath="$ctx:Person"/>
   <target type="body"/>
 </enrich>
  </sequence>
</target>
</iterate>

Los aspectos más interesantes dentro de cada iteración son:
  1. Se hace una copia del objeto person a una propiedad llamada Person. Esto con el objetivo de salvar ese objeto al realizar un call.
  2. Se invoca a la segunda API pasándole como parámetro el ID de la persona.
  3. De dicha invocación se obtiene un objeto usando el xpath $body//jsonObject//roles que es agregado como un hijo al contenido de la propiedad Person.
  4. Finalmente la propiedad Person es usada para reemplazar el contenido del body actual. Y con esto ya la respuesta está creada.

Lo último que se hace en la secuencia de entrada es mover el mensaje para la secuencia de salida usando el mediador LoopBack.

image

En la secuencia de salida los pasos son los siguientes:
image


Crear una propiedad que contenga el elemento padre sobre el cual se van a agregar las respuestas.

<property name="per" scope="default">
  <ns:Personas xmlns:ns="www.personas.org"/>
</property>

Luego hacemos uso del mediador Aggregate para agregar las respuestas que fueron direccionadas a la secuencia de salida.

<aggregate id="it1">
<completeCondition>
  <messageCount max="-1" min="-1"/>
</completeCondition>
<onComplete enclosingElementProperty="per" expression="$body/*[1]">
  <log>
 <property
   expression="json-eval($.Personas.person[0].givenName)" name="NOMBRE PERSONA: "/>
  </log>
  <send>
</onComplete>
</aggregate>

Dentro del onComplete ponemos un ejemplo de como se captura un valor del JSON, aunque no es necesario y luego incluimos el mediador send para enviar el mensaje de vuelta al cliente.
Y eso es todo. La respuesta final del API es la siguiente:
{
        "Personas":
        {
            "person":
            [
                {
                    "Id": 2,
                    "givenName": "shammi",
                    "lastName": "jagasingha",
                    "age": 30,
                    "contactInfos":
                    [
                        {
                            "InfoId": 1,
                            "department": 1,
                            "contactType": "email",
                            "value": "shammi@abc.org"
                        },
                        {
                            "InfoId": 2,
                            "department": 1,
                            "contactType": "mobile",
                            "value": 2222222222
                        },
                        {
                            "InfoId": 3,
                            "department": 1,
                            "contactType": "home",
                            "value": "Magic Dr,USA"
                        }
                    ],
                    "roles":
                    [
                        {
                            "personRoleId": 1,
                            "personKey": 2,
                            "role": "Manager"
                        },
                        {
                            "personRoleId": 2,
                            "personKey": 2,
                            "role": "QA"
                        }
                    ]
                },
                {
                    "Id": 1,
                    "givenName": "ajith",
                    "lastName": "vitharana",
                    "age": 25,
                    "contactInfos":
                    [
                        {
                            "InfoId": 1,
                            "department": 1,
                            "contactType": "email",
                            "value": "ajith@abc.org"
                        },
                        {
                            "InfoId": 2,
                            "department": 1,
                            "contactType": "mobile",
                            "value": 111111111
                        },
                        {
                            "InfoId": 3,
                            "department": 1,
                            "contactType": "home",
                            "value": "Magic Dr,USA"
                        }
                    ],
                    "roles":
                    [
                        {
                            "personRoleId": 1,
                            "personKey": 1,
                            "role": "Developer"
                        },
                        {
                            "personRoleId": 2,
                            "personKey": 1,
                            "role": "Engineer"
                        }
                    ]
                }
            ]
        }
    }
Como pueden ver se ha realizado sin mucha complicación una orquestación simple dentro del WSO2 ESB. Les dejo el código fuente que lo pueden importar en el WSO2 Developer Studio.

2 comentarios: