w w w . c h a k r a y . c o m
“How to get a Cup of Coffee the
WSO2 way” en Español. Juanjo Hernández Documento basado en el artículo de Mr. Hiranya Jayathilaka, titulado “How to GET a Cup of
Coffee the WSO2 Way”. El sentido del presente artículo es llevar a la comunidad hispano
hablante este recurso, por muchos considerado un buen ejemplo para comprender el
manejo de las REST API en WSO2 ESB, así como trasladar el ejemplo a las últimas
versiones de WSO2 y exponer los errores y problemas encontrados en su implementación.
2
Por Juanjo Hernández IT Consultant
Índice
1. Sobre este documento. ............................................................................................ 4
2. Consideraciones tecnológicas. ................................................................................ 5 2.1. Soporte REST en WSO2 middleware. .............................................................. 5 2.2. Soporte API REST en WSO2 ESB. ................................................................... 5 2.3. Mapeos de la URL y plantillas URI. ................................................................... 9 2.4. Configuración de APIs y resources en el ESB. ............................................... 10
3. Diseño de la Solución “Starbucks”. ........................................................................ 11
4. Configuración de los servidores ............................................................................. 14
5. Implementación. ..................................................................................................... 17 5.1. Realizando nuevos pedidos. ........................................................................... 17 5.2. Revisando pedidos. ......................................................................................... 22 5.3. Realizando pagos. ........................................................................................... 23 5.4. Manejando actualizaciones de pedidos. .......................................................... 25 5.5. Recuperando la lista de pedidos pendientes. .................................................. 27 5.6. Revisando el estado del pago. ........................................................................ 29 5.7. Eliminando pedidos completados de la lista. ................................................... 29
6. Mejorando la Solución en general. ........................................................................ 30 6.1. Negociando el formato del contenido (Content Negociation). ......................... 31 6.2. Aplicando Seguridad al API ............................................................................. 32 6.3. Mejorando el rendimiento (Caching). .............................................................. 35
7. Ejecución de la Solución. ....................................................................................... 36 7.1. Clientes GUI. ................................................................................................... 36
8. Problemas encontrados en la implementación original. ........................................ 38
9. Conclusiones. ........................................................................................................ 38
A. Referencias y recursos .......................................................................................... 39
B. Tabla de imágenes ................................................................................................ 39
Autor: ............................................................................................................................ 41
Revisado por: ............................................................................................................... 41
3
Por Juanjo Hernández IT Consultant
4
Por Juanjo Hernández IT Consultant
1. Sobre este documento.
La curva de aprendizaje en tecnologías ESB suele ser bastante pronunciada. WSO2
dispone de una comunidad no muy grande y en español la información técnica es
bastante escasa.
Muchas son las maneras de introducirse en WSO2 ESB, hay que tener en cuenta que
este ESB se basa y adopta todas las características de proyecto Apache Synapse. Es
este motor ESB y plataforma de intermediación la que es necesaria conocer en
profundidad para llevar a cabo la orquestación de los servicios con WSO2. Por lo que
un vistazo a la página de Documentación de Apache Synapse1 puede ser un buen
comienzo, además de que incorpora un buen número de ejemplos.
En general es más fácil asimilar los conceptos con ejemplos prácticos que permitan
ver a nivel funcional las teorías expresadas mediante arquitecturas TIC. Uno de los
ejemplos más completos e imprescindibles para comprender el uso y las técnicas de
implementación en WSO2 ESB es el ejemplo de Mr. Hiranya Jayathilaka “How to GET
a Cup of Coffee the WSO2 Way2”. Este ejemplo consiste en aplicar la intermediación
del ESB para transformar los mensajes de unos servicios SOAP publicados en un
servidor de aplicaciones a REST que es la manera que las aplicaciones clientes que
consumen los servicios aceptan.
La aplicación se basa en una hipotética implementación de comunicación entre los
clientes de una cafetería de la conocida cadena STARBUCKS con la central de
STARBUCKS que expone, en una capa de servicios SOAP, su OMS3. Los clientes de
dicha cafetería esperan comunicar con el OMS con REST y de aquí el uso de WSO2
ESB para intermediar, transformar, y en un futuro monitorizar y asegurar, dicha
comunicación.
1 http://synapse.apache.org/docs_index.html 2 http://wso2.com/library/articles/2012/09/get-cup-coffee-wso2-way/ 3 Order Management System. http://en.wikipedia.org/wiki/Order_management_system
5
Por Juanjo Hernández IT Consultant
Desde Chakray Consulting nos hemos propuesto traducir y mejorar si cabe, el ejemplo
de Mr. Hiranya Jayathilaka y detallar los problemas que nos hemos encontrado en la
implementación práctica del mismo.
2. Consideraciones tecnológicas.
2.1. Soporte REST en WSO2 middleware.
En este ejemplo vamos a usar dos productos que pertenecen a la suite WSO2
middleware, WSO2 ESB4 (WSO2 Enterprise Service Bus) y WSO2 AS5 (WSO2
Application Server)
El Servidor de Aplicaciones de WSO2 (AS), nos hará de servidor OMS y es desde
dónde se encuentra toda la funcionalidad para la gestión de las órdenes que los
clientes REST realizan, dicha funcionalidad se expone en una capa SOAP
El Bus de Servicios Empresarial de WSO2 (ESB), nos servirá para enrutar, filtrar y
transformar las llamadas RESTful. Cualquier proxy service o sequence desplegada en
WSO2 ESB pueden recibir y procesar llamadas REST. El ESB nos va a proporcionar
mecanismos para que la interacción entre clientes RESTful con servicios SOAP y
clientes SOAP con servicios RESTful sea sencilla y cómoda. A partir de la versión
4.0.3, WSO2 ESB también tiene soporte completo para exponer API REST. Esta
característica es la que centra este artículo, todo el trabajo de implementación aquí
descrito gira en torno a la exposición de servicios REST en WSO2 ESB.
2.2. Soporte API REST en WSO2 ESB.
Un API REST de WSO2 ESB es análogo a una aplicación web desplegada en el
runtime del ESB. Cada API está configurada con un contexto, definido por el usuario
con una URL. Este método de funcionamiento es muy similar a cómo funciona una
aplicación web desplegada en un contenedor de servlets6 que está vinculado en un
contexto con una URL fija. La API sólo procesará las solicitudes que entren mediante
4 http://wso2.com/products/enterprise-service-bus/ 5 http://wso2.com/products/application-server/ 6 http://www.oracle.com/technetwork/java/index-jsp-135475.html
6
Por Juanjo Hernández IT Consultant
el uso de la URL de su contexto. Por ejemplo, si una API tiene vinculado el contexto "/
test", sólo aquellas solicitudes HTTP cuya ruta URL comienza con "/ test" serán
“manejados” por esa API. Incluso es posible enlazar un API dado a un nombre de host
y o un número de puerto definido por el usuario.
WSO2 desde su versión 4.0.3 es capaz de manejar, mediar y exponer una API REST,
por lo que nuestro ejemplo, funcionará de la siguiente manera.
Ilustración 1. REST TO SOAP Mediación ESB
También es posible que pese que ambos entornos funcionen bajo los principios REST
la respuesta tenga que ser transformada en un formato que sea entendible por ambas
partes Cliente/Servidor, el ESB media en el mensaje y permite la comunicación entre
ambos participantes, a esto se le llama Content Negotiation y representa la capacidad
del ESB de cambiar el formato del mensaje de tal manera que pueda ser entendido
por ambas partes tras la modificación.
Ilustración 2. Transformación de mensajes REST con ESB
7
Por Juanjo Hernández IT Consultant
Continuando con la explicación de cómo WSO2 ESB da cobertura a las API REST,
empezaremos introduciendo el concepto de resource7. Un API REST está formado por
uno o más recursos. Desde un punto de vista de arquitectura, un recurso es un
componente lógico de una API que se puede acceder al hacer un tipo particular de
llamadas HTTP. Desde un punto de vista más pragmático, un resource es similar a un
proxy service. Al igual que un proxy service, un resource también contiene una in-
sequence, una out-sequence y una fault-sequence. Los mediadores disponibles son
los mismos y su funcionamiento idéntico. Pero también hay varias diferencias
significativas entre los resources y los proxy services, un resource sólo puede ser
utilizado para recibir y procesar llamadas REST, mientras que un proxy service puede
recibir todo tipo de peticiones. Un resource tampoco puede publicar un WSDL ni otros
WS-* (módulos de seguridad, mensajería fiable, etc).
Como hemos comentado anteriormente cada resource puede ser asociado con un
patrón de URL definido por el usuario o una plantilla URI. Esta metodología puede
restringir el tipo de petición HTTP procesada por un resource en particular. Además un
resource también puede estar limitado a un subconjunto de acciones HTTP (GET,
PUT, POST, DELETE…) o valores de cabecera. Esta opción añade un control
adicional sobre cómo las peticiones son manejadas por un resource dado.
Por ejemplo vamos a considerar un resource asociado con el patrón de URL “/foo/” y la
acción HTTP GET. Esta configuración nos asegura que dicho resource solo será
procesado cuando se realicen peticiones GET las cuales coincidan con el patrón del
path de la URL “/foo/, por lo tanto las siguientes peticiones serán procesadas y
mediadas por el resource configurado en la api:
GET /test/foo/bar
GET/test/foo/a?arg1=hello
Y las siguientes peticiones no serán procesadas por ese resource:
GET /test/food/bar (URL pattern not matching)
POST /test/foo/bar (HTTP verb not matching)
7 Se ha optado por convención no traducir los elementos de la sintaxis ESB con el fin de simplificar la implantación.
8
Por Juanjo Hernández IT Consultant
Una vez que la petición se envía al resource será mediada a través de la in-sequence
del recurso. Al final de la in-sequence la solicitud será enviada a la aplicación de back-
end para su posterior proceso. Las respuestas provenientes del sistema de back-end
será mediadas por la API en el ESB a través de la out-sequence del resource.
Para terminar la explicación del funcionamiento de un resource configurado en una
API comentar que la fault-sequence sirve para controlar los errores que se produzcan
durante la mediación del mensaje en el resource.
Para entender mejor cómo funciona la gestión de las solicitudes (request) entre las API
y los resources, vamos a considerar la siguiente jerarquía de objetos:
Ilustración 3. Configuración de contextos y plantillas de URL en resource.
Aquí tenemos dos APIs REST configuradas para los contextos “/foo” y “/bar”
respectivamente. Cada API habilita dos resources. La petición GET del path “/foo/test”
será recibida por la primera API1 ya que responde a este contexto. Esta API1 maneja
dos recursos uno para GET y otro para POST, cuando se realice una petición GET
será el resource A el que gestionará y mediará el mensaje y cuando se realice una
petición POST será el resource B el que cogerá la batuta en el manejo del flujo. De
9
Por Juanjo Hernández IT Consultant
idéntica manera funcionará la API2, que tiene un contexto “/bar”, todas la peticiones
que se realicen bajo ese concepto serán redirigidas a esta API y según la petición y,
en este caso también, el path de la url, el mensaje será mediado por el resource C o
resource D (peticiones PUT con path /bar/test será mediado por resource C y
peticiones DELETE con path /bar/abc será mediado por resource D).
Todo lo anteriormente mencionado, explica la gran utilidad del uso de las
características de las API REST en WSO2 ESB.
En pocas palabras, se proporciona un método simple pero muy potente para romper el
flujo de llamadas HTTP basado en: métodos HTTP, patrones de URL y otros
parámetros. Una vez que las llamadas HTTP han sido filtradas y enviadas a las API y
a sus resources pertinentes, podemos someterlos a las capacidades de enrutamiento
y de mediación del ESB utilizando mediators, sequences y endpoints.
2.3. Mapeos de la URL y plantillas URI.
Cómo hemos comentado en el apartado anterior un resource puede estar asociado
con un mapeo de URL o con una plantilla URI. Un mapeo URL puede ser cualquier
mapeo de servlet válido, por lo tanto y siguiendo las especificaciones servlet, hay tres
tipo de mapeos URL:
Mapeo del path: /test*, /foo/bar/* Mapeo por extensions *.jsp, *.do Mapeo exacto /test, /test/foo
Cuando un resource está definido con un Mapeo de URL de los anteriormente
comentados, solo aquellas peticiones que coincidan con el mapeo serán procesadas
por el resource. Alternativamente se puede configurar un resource con una plantilla
URI. Una plantilla URI representa una clase de URI que usa patrones y variables,
algún ejemplo de plantillas de URIs válidas serían:
/order/{orderId}
/dictionary/{char}/{word}
10
Por Juanjo Hernández IT Consultant
Los identificadores entre llaves son consideradas variables. Un ejemplo de URL que
coincidiría con uno de los templates anteriores sería: /order/A001
En la URL anterior a la variable orderId se le ha asignado el valor “A0001”. De forma
similar la siguiente URL: /dictionary/c/cat coincide con la plantilla
“/dictionary/{char}/{Word}”. En este caso la variable “char” asume el valor “c” y la
variable “word” el valor “cat”.
El caso en que un resource se asocia con una plantilla URI, todas las peticiones que
coincidan con la plantilla serán procesadas por el resource. Al mismo tiempo el ESB
proporcionará acceso a los valores de las variables configuradas en la plantilla a
través de las propiedades de contexto del mensaje. Siguiendo con el ejemplo de la
plantilla URI anteriormente comentada "/dictionary/{char}/{word}", cuando la solicitud
"/dictionary/c/cat" se envíe al ESB, el recurso arriba mencionado será capaz de
recuperar los valores exactos usando la extensión XPath de WSO2 ESB y más
concretamente con el método get-property. Por ejemplo para escribir en el log los
valores recuperados en la petición de la siguiente manera utilizando el log mediator
<log level="custom">
<property name="Character" expression="get-property('uri.var.char')"/>
<property name="Word" expression="get-property('uri.var.word')"/>
</log>
2.4. Configuración de APIs y resources en el ESB.
Ahora que hemos conseguido una básica comprensión de cómo funcionan las APIs
REST en WSO2 ESB, vamos a echar un vistazo a cómo podemos configurar una API
en el Bus usando el lenguaje de configuración del ESB.
La definición de una API se identifica por el tag <api>. Cada API debe tener un nombre
y un contexto de URL únicos. Algunos ejemplos de definiciones de APIs:
<api name="API_1" context="/order">
<resource url-mapping="/list" inSequence="seq1" outSequence="seq2"/>
</api>
11
Por Juanjo Hernández IT Consultant
<api name="API_2" context="/user">
<resource url-mapping="/list" methods="GET" inSequence="seq3" outSequence="seq4"/>
<resource uri-template="/edit/{userId}" methods="PUT POST" inSequence="seq5"
outSequence="seq6"/>
</api>
<api name="API_3" context="/payments">
<resource url-mapping="/list" methods="GET" inSequence="seq7" outSequence="seq8"/>
<resource uri-template="/edit/{userId}" methods="PUT POST" outSequence="seq9">
<inSequence>
<log/>
<send>
<endpoint key="BackendService"/>
</send>
</inSequence>
</resource>
<resource inSequence="seq10" outSequence="seq11"/>
</api>
Si observamos la última definición del resource en la API_3, no se especifica un
mapeo URL ni tampoco una plantilla URI. La llamada a este resource se realizará por
defecto cuando la petición no cumpla ninguna de las condiciones anteriores. Cada API
puede tener como máximo un resource por defecto. En el ejemplo de la API_3 una
petición DELETE8 para la URL “/payments” será tratada por el resource por defecto ya
que ninguno de los otros está configurado para aceptar peticiones DELETE.
3. Diseño de la Solución “Starbucks”.
Ahora ya tenemos suficiente base de conocimiento de WSO2 AS y WSO2 ESB para
empezar, si se desea profundizar más se puede consultar la documentación de WSO2
ESB9. Vamos a implementar la solución RESTful que describe Jim Webber10 en su
artículo “How to GET a Cup of Coffee11” usando las tecnologías WSO2, es muy
recomendable leerse ese artículo antes de continuar para entender la solución
funcional que propone el autor y de esta manera entender ampliamente lo que aquí se
explica.
8 En estos momentos, DELETE no está implementado en este documento. 9 Un punto inicial de consulta es: https://docs.wso2.org/display/ESB480/Getting+Started+with+REST+APIs 10 http://jimwebber.org/ 11 http://www.infoq.com/articles/webber-rest-workflow/
12
Por Juanjo Hernández IT Consultant
La aplicación Starbucks12 descrita en el artículo de Jim Webber consiste en dos
máquinas diferentes:
• Customer state machine (Máquina de estado de Cliente) • Barista state machine (Máquina de estado de Barista13)
La máquina de estado de Cliente consiste en las siguientes interacciones:
• El cliente efectúa el pedido de una bebida. • El cliente realiza cambios en un pedido ya efectuado (p.e. pedir un condimento
para que sea añadido). • El cliente realiza e pago del pedido.
Por otra parte, la máquina de estado de Barista se compone de las siguientes interacciones:
• Recuperar una lista de los pedidos pendientes. • Comprobar si se ha recibido el pago de un pedido en particular • Borrar los pedidos ya emitidos de la lista de pedidos pendientes.
Basándonos en los detalles anteriores, podemos identificar tres aplicaciones principales involucradas en la solución:
• Aplicación de Cliente14 • Aplicación de Barista • Starbucks OMS (Order Management System o Sistema Gestor de Pedidos)
La Aplicación de Cliente deberá interactuar con el OMS de Starbucks para añadir los
pedidos y hacer los pagos. De una manera similar la Aplicación de Barista deberá
comunicar con el OMS para obtener la lista de las órdenes pendientes y comprobar los
estados de pago de lo cada pedido requerido. Con esto en mente podemos llegar a
dar a este escenario una solución de arquitectura de alto nivel, como la siguiente:
12 Cadena de cafeterías con puntos de venta en todo el mundo. http://www.starbucks.com 13 Barista: Profesional especializado en el café de alta calidad, que trabaja creando nuevas y diferentes bebidas basadas en él, usando varios tipos de leches, esencias y licores, entre otros. También es el responsable de la presentación de las bebidas y puede complementar su trabajo con arte del latte. (Extraído de Wikipedia) 14 Cliente en este caso lo entendemos como persona que realiza un pedido en una cafetería de Starbucks y no como aplicación cliente TIC.
13
Por Juanjo Hernández IT Consultant
Ilustración 4. Solución de arquitectura de alto nivel.
En el actual ejemplo no vamos a prestar atención a la implementación de las 3
aplicaciones, nosotros estamos más interesados en cómo las aplicaciones interactúan
entre ellas usando un modelo de comunicación RESTful. Para la aplicación Starbucks
OMS vamos a usar una aplicación simple Web Service de Java la cual estará alojada
en el servidor de aplicaciones (WSO2 AS) y que expone una capa de servicios SOAP.
Todos los detalles de los pedidos se almacenan en una tabla en memoria, no vamos a
utilizar base de datos o ficheros en este ejemplo para la persistencia, aunque como es
normal si fuera una aplicación para ser usada en un escenario real el almacenamiento
de la información y toda la capa de datos requeriría un análisis diferenciado.
Para simular la Aplicación de Cliente y la Aplicación de Barista vamos a usar cURL15,
que, para quien no la conozca, es una herramienta cliente muy simple de HTTP que
nos permite realizar las llamadas a la capa REST usando cualquier tipo de petición y
modificando los tipos de contenido (Content Type) que vamos a transmitir. Aun así
existen un par de aplicaciones simples realizadas con Java con una interfaz más
amigable y que simularían los comportamientos de la Aplicación de Cliente y de
Barista.
Cómo nuestra Aplicación OMS de Starbucks está basada en SOAP tenemos que
desarrollar el método de conversión de REST a SOAP y es por este motivo que
vamos a usar WSO2 ESB. Expondremos un conjunto de APIs REST en el ESB. Las
Aplicaciones de Cliente y de Barista interaccionarán con estas APIs realizando
invocaciones RESTful. Posteriormente el ESB “traducirá” las llamadas REST a SOAP
mediando los mensajes y realizará las invocaciones pertinentes al OMS que se
15 http://curl.haxx.se/
14
Por Juanjo Hernández IT Consultant
encuentra en el servidor de aplicaciones de WSO2 (AS). Con esta información
podemos mejorar el diagrama de la arquitectura de la siguiente manera:
Ilustración 5. Solución de arquitectura con ESB.
Esta solución, además de servirnos para el diseño del sistema RESTful, nos enseñará
cómo exponer un sistema existente sobre REST. Básicamente tomamos el OMS
Starbucks basado en SOAP y le aplicamos una capa RESTful sobre él. La misma
técnica se puede ampliar para exponer cualquier aplicación a través de REST. Muchas
organizaciones cuentan con servicios y aplicaciones legacy que necesitan ser
expuestos sobre REST para que puedan ser fácilmente consumidos por los
navegadores web y apps móviles actuales. Este artículo y el ejemplo que trata
pretenden proporcionar la base para la gestión de cualquier escenario similar.
4. Configuración de los servidores
En el artículo original de Mr. Hiranya Jayathilaka se usaron las versiones de productos
que por aquel entonces (septiembre 2012) WSO2 tenía, en este ejemplo nos hemos
basado en las versiones que actualmente están disponibles y son WSO2 AS 5.2.1 y
WSO2 ESB 4.8.1. Para instalar los servidores y permitir que corran la máquina en la
que realicemos la práctica debemos cambiar el parámetro Offset de nuestro archivo
carbon.xml que se encuentra en: repository/conf/carbon.xml de la siguiente manera:
<Ports>
<Offset>1</Offset>
[…]
</Ports>
Para cada servidor necesitamos cambiar el Offset, para los recién llegados a la suite
de aplicaciones middleware de WSO2 comentar únicamente que el número introducido
15
Por Juanjo Hernández IT Consultant
en Offset incrementa el puerto por defecto en el que corren los servidores en el
número en cuestión. Es decir si el servidor corre en el puerto https 9443 por defecto,
un Offset de 1 haría que el servidor corriera en el puerto 9444 y un Offset de 3 haría
que corriera en el puerto 9446 lo que evitaría posibles conflictos de puertos ocupados
al arrancar.
Una vez realizado esto y arrancadas ambas máquinas, ejecutando el script
wso2server que se encuentra en el directorio bin de cada instalación, instalaremos
inicialmente la Aplicación Starbucks OMS en el WSO2 AS. Para ello nos
identificaremos en la zona de administración del AS en
https://localhost:9443[+Offset]/carbon, haremos clic en Services>Add>AAR Service del
menú Manage y lo subiremos el archivo StarbucksOutletService.aar16.
Ilustración 6. Añadir Servicios Starbucks back-end en WSO2 AS.
Tras algunos segundos de despliegue ya tienes disponible la Aplicación Starbucks
OMS, haciendo clic en Services>List puedes ver un nuevo servicio llamado
StarbucksOutletService en el servidor.
16 https://www.dropbox.com/s/b3leesblvkm2eyg/StarbucksOutletService.aar
16
Por Juanjo Hernández IT Consultant
Ilustración 7. Lista de servicios desplegados.
Ilustración 8. Detalle del servicio Starbucks OMS desplegado.
17
Por Juanjo Hernández IT Consultant
Para realizar las pruebas funcionales previas se puede usar un cliente SOAP
cualquiera como SOAP UI17 para interactuar con los servicios y sus acciones, también
con la opción Try-It del propio AS de WSO2 se podría invocar el servicio y probarlo.
5. Implementación.
5.1. Realizando nuevos pedidos.
Comenzamos con la implementación de la API REST responsable de aceptar nuevos
pedidos. El back-end OMS de Starbucks proporciona una operación addOrder que
permite el manejo de esta tarea. De esta manera nuestra API REST debería al final,
invocar esta operación para realizar la funcionalidad. Podemos llamar a nuestra API
“StarbucksOrderAPI” y referenciarla al contexto “/order” como sigue:
<api name="StarbucksOrderAPI" context="/order"> […] </api>
Conforme el mencionado artículo de Jim Webber, un nuevo pedido es enviado usando
una llamada HTTP POST a la URL de contexto “/order”, entonces y según hemos
vistos en capítulos anteriores tenemos que definir un resource en nuestra nueva API
para aceptar peticiones POST, tal que así:
<api name="StarbucksOrderAPI" context="/order"> <resource url-mapping="/" methods="POST"> […] </resource> </api>
Ahora podemos avanzar en la implementación de la in-sequence y la out-sequence del
resource anterior. La in-sequence debe construir el mensaje SOAP esperado por la
operación addOrder del Starbucks OMS e invocar esa operación.
<inSequence> <property name="STARBUCKS_HOST_NAME" expression="$axis2:SERVICE_PREFIX" /> <payloadFactory> <format> <m0:addOrder> <m0:drinkName>$1</m0:drinkName> <m0:additions>$2</m0:additions> </m0:addOrder> </format>
17 http://www.soapui.org/
18
Por Juanjo Hernández IT Consultant
<args> <arg expression="//sb:drink" /> <arg expression="//sb:additions" /> </args> </payloadFactory> <send> <endpoint key="DataServiceEndpoint" /> </send> </inSequence>
Por otra parte la out-sequence debe transformar la respuesta SOAP en un formato de
mensaje RESTful aceptado, establecer el código de estado conveniente y pasar el
mensaje al cliente:
<outSequence> <property name="HTTP_SC" value="201" scope="axis2" /> <property name="uri.var.orderId" expression="//m1:orderId"/> <sequence key="StarbucksOrderInfo" /> <send /> </outSequence>
El fichero de configuración completo de la API de este artículo está disponible y te lo
puedes descargar, este es el fichero en cuestión: synapse-configs.zip18. Basta con
extraer cada fichero xml en su carpeta específico a partir del directorio del servidor,
repository/deployment/server/synapse-configs/default del ESB y substituir la
configuración existente con la descargada del presente ejemplo. Recordad que se
debe reiniciar el servidor. Una vez descargado y desplegado correctamente, puedes
invocar la StarbucksOrderAPI y probarla. Si estás usando Curl, puedes crear un
fichero llamado order.xml con el siguiente contenido,
<?xml version="1.0" encoding="UTF-8"?> <order xmlns="http://starbucks.example.org"> <drink>Caffe Misto</drink> </order>
posteriormente usando el siguiente comando:
curl -v -d @order.xml -H "Content-type: application/xml" http://localhost:8281/order
La respuesta debe ser algo similar a: POST /order HTTP/1.1 Transfer-Encoding: chunked Content-Type: application/xml Host: 127.0.0.1:8281 Connection: Keep-Alive User-Agent: Apache-HttpClient/4.1.2 (java 1.5) <?xml version="1.0" encoding="UTF-8"?>
18 https://www.dropbox.com/sh/rggaomh9d3f0aym/bUo3nQA1GD
19
Por Juanjo Hernández IT Consultant
<order xmlns="http://starbucks.example.org"> <drink>Caffe Misto</drink> </order> HTTP/1.1 201 Created Content-Type: application/xml; charset=UTF-8 Location: http://127.0.0.1:8281/order/7b36032d-6aa2-4d77-90a9-7473a42de77b Server: WSO2 Carbon Server Vary: Accept-Encoding Date: Wed, 15 Aug 2012 12:55:50 GMT Transfer-Encoding: chunked Connection: Keep-Alive <?xml version="1.0" encoding="UTF-8"?> <order xmlns="http://starbucks.example.org"> <drink>Caffe Misto</drink> <cost>6.99</cost> <additions/> <next rel="http://127.0.0.1:8281/payment" type="application/xml" uri="http://127.0.0.1:8281/payment/order/7b36032d-6aa2-4d77-90a9-7473a42de77b" xmlns="http://example.org/state-machine"/> </order>
Esta orden realiza la petición POST para introducir un Pedido en el sistema Starbucks
OMS desde nuestro cliente que envía REST y tras pasar por el ESB transformado a
SOAP y la respuesta que proviene del Starbucks OMS que originalmente es SOAP
transformada en REST/POX para que sea aceptada por el cliente.
En este punto se debe comentar que según la versión actual del ESB (que usa Axis2
para gestionar los servicios) es necesario realizar algunas modificaciones en la
implementación de la API que Mr. Hiranya Jayathilaka no aplicaba seguramente
porque la versión del ESB/Axis no se lo exigía. Es necesario añadir un mediador del
tipo property tras la manipulación del mensaje y antes de realizar el envío del mismo
formateado al Starbucks OMS. Esta propiedad que se debe incluir debe especificar
qué operación SOAP (SOAPAction) va a invocarse en la llamada de la siguiente
manera: <property name="SOAPAction" value="urn:updateOrder" scope="transport"></property>
Esta pequeña modificación es necesaria para no recibir el molesto y poco amigable
error:
Caused by: org.apache.axiom.soap.SOAPProcessingException: First Element must contain the local name, Envelope , but found faultstring
Continuamos con la configuración de la API e intentar entender cómo funciona. La
primera cosa que nos encontramos es el siguiente mediador del tipo property y que se
usa en la in-sequence: <property name="STARBUCKS_HOST_NAME" expression="$axis2:SERVICE_PREFIX" />
20
Por Juanjo Hernández IT Consultant
SERVICE_PREFIX es una propiedad disponible en el flujo de mensajes del ESB.
Establecida en el ámbito axis2, esta propiedad contiene el segmento
[protocolo]://[host]:[port] de la URL que será invocado por el cliente.
Por ejemplo, si el usuario envía una petición a la URL http://localhost:8280/foo/bar,
entonces la propiedad anterior contendrá el valor http://localhost:8280/. Usando el
mediador property anterior asignamos ese valor a una nueva propiedad llamada
STARBUCKS_HOST_NAME. Esto lo hacemos así porque tenemos la intención de
utilizar este valor más adelante en la creación de la respuesta que posteriormente
debe ser enviada al cliente.
Tras esto vemos otro mediador llamado payloadFactory. Este mediador es usado para
construir el mensaje SOAP que necesita ser enviado al OMS de Starbucks. Para poder
extraer la información del mensaje usamos expresiones XPath19 sobre la petición
original para extraer algunos de sus valores y usarlos en la construcción del nuevo
mensaje que ejecutará la acción addOrder.
Finalmente en el mismo apartado in-sequence se usa un mediador del tipo send con
un endpoint. Aquí es donde realizamos las llamadas al back end OMS de Starbucks
según la configuración de tu Offset la definición del endpoint sería: <endpoint name="DataServiceEndpoint" xmlns="http://ws.apache.org/ns/synapse"> <address uri=http://localhost:9763/services/StarbucksOutletService format="soap12"/> </endpoint>
Cómo apunte podemos observar el atributo soap12. Esto informa al ESB que todos los
mensajes enviados al endpoint del OMS deberían ser en formato SOAP 1.2, por lo
tanto el ESB envolverá el mensaje XML en un envoltorio (envelope) SOAP 1.2 antes
de enviarlo al servicio SOAP back-end.
Si todo funciona correctamente, nuestro ESB recibirá a continuación la respuesta
SOAP de nuestro AS. Apuntar cómo el back-end OMS ha generado un ID único para
nuestro pedido. Este mensaje SOAP será mediado a través de la out-sequence del
resource apropiado en la API. En nuestra out-sequence tenemos la siguiente instancia
de mediador de tipo property como primera entrada: <property name="HTTP_SC" value="201" scope="axis2" />
19 http://www.w3.org/TR/xpath20/
21
Por Juanjo Hernández IT Consultant
Este mediador cambia el código de estado HTTP de la respuesta. De acuerdo con los
principios de diseño RESTful tendríamos que envíar una respuesta con código de
estado 201 Created en este escenario. Pero nosotros obtenemos del back-end OMS el
típico mensaje 200 “OK” que es el comportamiento SOAP habitual. Esta configuración
del mediador cambia el estado a 201 Created para que el cliente reciba la respuesta
correcta.
Continuado con el diseño de una arquitectura de servicios RESTful correcta, es
también muy importante enviar una cabecera Location junto con la respuesta 201
Created. Esta cabecera debe contener una URL válida que apunte de nuevo al recurso
que se ha creado en el sistema back-end, por lo tanto en nuestro escenario la
cabecera Location debe contener una URL mediante la cual podemos obtener una
descripción del pedido que acabamos de crear. El siguiente mediador property se
encuentra en la sequence “StarbucksOrderIndo” y es usado para añadir la cabecera
Location para la respuesta “saliente”. <property name="Location" expression="concat($ctx:STARBUCKS_HOST_NAME, 'order/', //m1:orderId)" scope="transport" />
Como puedes ver aquí usamos la propiedad que inicializamos anteriormente en la in-
sequence para poder recuperarla cuando fuera necesario
STARBUCKS_HOST_NAME. Simplemente recuperamos el valor de esta propiedad y
le añadimos el fragmento “/order/orderId” para construir la URL completa.
La construcción de respuestas que contienen direcciones URL a los diferentes
recursos relacionados es de suma importancia en una arquitectura de aplicaciones
REST.
Esto facilita particularmente la navegación y las transiciones de estados en una
aplicación que siga el paradigma de integración REST. La respuesta de una llamada
REST debe contener toda la información que un cliente requeriría para navegar y
proceder con el siguiente paso dentro de la aplicación, de esta manera el cliente
puede comenzar con una única URL conocida y navegar toda la aplicación siguiendo
las URLs incluidas en cada respuesta. A este concepto se le denomina a menudo
como HATEOAS (Hypermedia as the Engine of Application State). Es interesante que
reflexionemos sobre cómo hemos incorporado esta función en nuestra solución
utilizando las capacidades de mediación de WSO2 ESB. La respuesta a la solicitud de
22
Por Juanjo Hernández IT Consultant
creación del pedido contiene una cabecera Location que apunta de nuevo al recurso
pedido. También el mensaje de respuesta contiene más direcciones URL para que las
acciones de Pago de Pedido se encarguen de su tramitación. Usando la propiedad
SERVICE_PREFIX incorporada en el ESB construimos todas los URLs para que todas
apunten de nuevo al ESB. En ningún momento se exponen los detalles del endpoint
del servicio web del back-end OMS.
5.2. Revisando pedidos.
Una vez el pedido ha sido añadido al sistema, el cliente debería ser capaz de revisarlo.
Esto se puede hacer enviando peticiones HTTP GET a la URL especificada en la
respuesta 201 Created que comentábamos en el capítulo anterior. Tal y como se ha
explicado y siguiendo el paradigma HATEOAS la misma respuesta de la creación del
nuevo pedido incorpora la URL que permitió esa acción.
En la configuración de nuestra API hemos definido un resource separado para
procesar las peticiones GET. <resource uri-template="/{orderId}" methods="GET PUT OPTIONS" faultSequence="StarbuckFault">
Hemos usado una plantilla URI para especificar el formato de la petición URL entrante
que esperamos. La parte orderId de la URL ha sido especificada como variable porque
cada orden tendrá su identificador único. En la in-sequence construimos un mensaje
getOrder SOAP usando el mediador payloadFactor. Es aquí donde utilizaremos la
variable orderId. <payloadFactory> <format> <m0:getOrder> <m0:orderId>$1</m0:orderId> </m0:getOrder> </format> <args> <arg expression="$ctx:uri.var.orderId" /> </args> </payloadFactory>
La respuesta de la aplicación Starbucks OMS será un mensaje SOAP que contendrá
los detalles del pedido. Simplemente convertimos en un documento POX (plain old
XML) y lo devolvemos al cliente como una respuesta RESTful. Para probar el
escenario, debemos localizar un orderId de un pedido realizado anteriormente (se
puede conseguir de la cabecera Location de la respuesta al realizar una petición de
23
Por Juanjo Hernández IT Consultant
creación de nuevo Pedido) y ejecutar Curl como sigue (reemplazando my-order-id, por
el identificador del pedido, orderId, conseguido) curl -v http://localhost:8281/order/my-order-id
Llegados a este punto es un buen momento para demostrar las capacidades de
manejo de errores del ESB. Si ejecutamos la siguiente orden Curl como sigue con un
ID incorrecto: curl -v http://localhost:8281/order/bogus-order-id
El OMS de Starbucks devolverá un mensaje vacío al ESB. Veamos, la out-sequence
que corresponde al resource de la API ha sido configurado para detectar esta
condición y responder un estado HTTP 404 Not Found response. Por lo tanto si
intentas ejecutar el comando anterior se devolverá algo similar a lo siguiente: HTTP/1.1 404 Not Found Content-Type: application/xml; charset=UTF-8 Server: WSO2 Carbon Server Date: Wed, 15 Aug 2012 13:10:10 GMT Transfer-Encoding: chunked <message xmlns="http://starbucks.example.org"><text>No order exists by the specified ID</text></message>
5.3. Realizando pagos.
Vamos a saltarnos un par de pasos y ver cómo se manejan los pagos en nuestra
solución. En el artículo de Webber, los pagos son tratados como un grupo separado de
varios resource.
En nuestra implementación manejamos los pagos mediante una API separada. Vamos
a llamar a esta API StarbucksPaymentAPI y asociarle el contexto “/payment”. El cliente
puede crear un pago enviando una petición del tipo PUT a la URL
http://localhost:8281/payment/order/orderId. El mensaje de la llamada debe contener
toda la información requerida para el pago así como el precio total y los detalles de la
tarjeta de crédito.
<resource
uri-template="/order/{orderId}"
methods="GET PUT"
faultSequence="StarbucksFault">
Observar que el resource ha sido configurado para el manejo de las peticiones PUT. El
resource, como ya hemos explicado, transformará el mensaje entrante en un mensaje
SOAP e invocará al back-end Starbucks OMS. La respuesta será transformada en su
24
Por Juanjo Hernández IT Consultant
regreso a un mensaje POX y enviada al cliente con un estado de respuesta 201
Created.
Tal y como hemos hecho en todas las operaciones anteriores y para seguir con el
diseño correcto de una arquitectura REST es conveniente añadir en la cabecera de la
respuesta un mediador property llamado Location que apuntará a la URL invocadora
perteneciente al resource de pago.
Para probar esta API podemos crear un fichero XML llamado payment.xml con la
siguiente información: <?xml version="1.0" encoding="UTF-8"?> <payment xmlns="http://starbucks.example.org"> <cardNo>1234-5678-9010</cardNo> <expires>12/15</expires> <name>Peter Parker</name> <amount>6.99</amount> </payment>
Ahora lo ejecutamos con el siguiente comando Curl (sustituyendo como anteriormente
el order-id por un id de un pedido real) curl -v -X PUT -d @payment.xml -H "Content-type: application/xml" http://localhost:8281/payment/order/order-id
La respuesta debería ser algo similar a: HTTP/1.1 201 Created Content-Type: application/xml; charset=UTF-8 Location: http://127.0.0.1:8281/payment/order/090abb1b-9da0-4eb3-86c1-02c7fa157514 Server: WSO2 Carbon Server Date: Wed, 15 Aug 2012 13:12:15 GMT Transfer-Encoding: chunked Connection: Keep-Alive <?xml version="1.0" encoding="UTF-8"?> <payment xmlns="http://starbucks.example.org/"> <cardNo>1234-5678-9010</cardNo> <expires>12/15</expires> <name>Peter Parker</name> <amount>6.99</amount> </payment>
Una vez el pago ha sido realizado, el cliente puede revisar los detalles del pago
realizando una petición GET. Recordad que para revisar el estado necesitamos el Id
único de la orden que se ha pagado en el punto anterior y ejecutar la siguiente orden
Curl (sustituye de nuevo order-id por el id real de la orden pagada que se quiere
revisar). curl -v http://localhost:8281/payment/order/order-id HTTP/1.1 200 OK Content-Type: application/xml; charset=UTF-8 Server: WSO2 Carbon Server
25
Por Juanjo Hernández IT Consultant
Date: Wed, 15 Aug 2012 13:17:39 GMT Transfer-Encoding: chunked Connection: Keep-Alive <?xml version="1.0" encoding="UTF-8"?> <payment xmlns="http://starbucks.example.org/"> <cardNo>1234-5678-9010</cardNo> <expires>12/15</expires> <name>Peter Parker</name> <amount>6.99</amount> </payment>
La Aplicación de Barista de Starbucks puede usar la característica anterior para
comprobar si un pedido ha sido pagado antes de que la bebida haya sido entregada al
cliente. Si el pago no ha sido hecho para un pedido en concreto, la API devolverá una
respuesta con el error 404 Not Found.
5.4. Manejando actualizaciones de pedidos.
Todos hemos ido a tomar un café en algún momento y aunque hemos pedido un café
solo, luego creemos que sería mejor dejar la cafeína por un día y tomar otra cosa por
ejemplo, un zumo. Nuestro sistema debe permitir al cliente realizar este tipo de
rectificaciones, pero ¿Qué pasa si ya ha comenzado el Barista a preparar nuestro
pedido? Entonces nuestro sistema no nos permitirá modificarlo y un sonriente
camarero nos dirá: “lo siento pero su pedido ya ha sido realizado”. Por lo tanto nuestra
solución debe permitir modificaciones en los pedidos hasta que el Barista haya
comenzado la preparación.
En el artículo original, Mr. Jim Webber propone usar el método HTTP OPTIONS de
manera que comprobemos cuando un pedido es modificable o no. Una simple petición
OPTIONS de una orden debería devolver en la respuesta una cabecera HTTP Allow.
Si el pedido aún se puede cambiar la cabecera Allow debería incluir dos valores GET y
PUT, sin embargo si el pedido ya ha pasado a realización y por lo tanto no se puede
modificar, únicamente debería devolver el valor GET que significa que ese pedido es
de “solo lectura”.
Permitir este requisito en nuestra implementación es fácil. Simplemente se debe añadir
el método OPTIONS a la lista de métodos soportados por el resource getOrder de la
siguiente manera: <resource uri-template="/{orderId}" methods="GET PUT OPTIONS" faultSequence="StarbuckFault">
26
Por Juanjo Hernández IT Consultant
Un simple switch case puede filtrar del todo las llamadas OPTIONS en el resource: <switch source="$ctx:REST_METHOD"> <case regex="OPTIONS"> <property name="NO_ENTITY_BODY" value="true" scope="axis2" type="BOOLEAN" /> <filter source="//m1:locked" regex="false"> <then> <property name="Allow" value="GET,PUT" scope="transport" /> </then> <else> <property name="Allow" value="GET" scope="transport" /> </else> </filter> </case> […] </switch>
El resource puede, entonces, consultar el OMS para comprobar si el pedido es
modificable o no, dependiendo de la respuesta del back-end, se podrá realizar una
respuesta HTTP apropiada en el ESB.
Realiza la prueba con el siguiente comando Curl (cómo siempre sustituye order-id por
un Id de pedido original) curl -v -X OPTIONS http://localhost:8281/order/order-id
Si la orden es modificable, la respuesta que se realizará debe ser algo similar a: HTTP/1.1 200 OK Allow: GET,PUT Server: WSO2 Carbon Server Date: Wed, 15 Aug 2012 13:22:36 GMT Content-Length: 0 Connection: Keep-Alive
Las actualizaciones de los pedidos se llevan a cabo haciendo llamadas HTTP PUT. El
mensaje de la petición debe contener una descripción de la actualización de la orden.
Hemos configurado nuestro resource en la API para manejar las invocaciones PUT de
la siguiente manera: <resource uri-template="/{orderId}" methods="GET PUT OPTIONS" faultSequence="StarbuckFault">
Para probar esta operación copia el siguiente xml en un fichero llamado update.xml y
ejecútalo con el Comando Curl que aparece a continuación (no te olvides, sustituye de
nuevo order-id por el id del pedido real) <?xml version="1.0" encoding="UTF-8"?> <order xmlns="http://starbucks.example.org"> <drink>Caffe Misto</drink> <additions>Milk</additions> </order>
curl -v -X PUT -d @update.xml -H "Content-type: application/xml" http://localhost:8281/order/order-id
27
Por Juanjo Hernández IT Consultant
Si todo va bien, se debe recibir un estado HTTP 200 confirmando la actualización del
pedido parecido a: HTTP/1.1 200 OK Content-Type: application/xml; charset=UTF-8 Location: http://127.0.0.1:8281/order/764cc8b5-e612-47e1-818b-225cb35b0c3f Server: WSO2 Carbon Server Date: Wed, 15 Aug 2012 13:24:48 GMT Transfer-Encoding: chunked Connection: Keep-Alive <?xml version="1.0" encoding="UTF-8"?> <order xmlns="http://starbucks.example.org"> <drink>Caffe Misto</drink> <cost>10.71</cost> <additions>Milk</additions> <next rel="http://127.0.0.1:8281/payment" type="application/xml" uri="http://127.0.0.1:8281/payment/order/764cc8b5-e612-47e1-818b-225cb35b0c3f" xmlns="http://example.org/state-machine"/> </order>
5.5. Recuperando la lista de pedidos pendientes.
Estamos a punto de finalizar las implementaciones que tienen que ver con las
interacciones entre el cliente y el back-end de Starbucks OMS. Los clientes ya pueden
realizar el pedido de una bebida, revisarlo, actualizarlo y también realizar los pagos,
por lo que ahora ha llegado el momento de implementar las interacciones entre el
barista y el OMS. La primera interacción que vamos a implementar es la lista de
pedidos pendientes. El web service OMS provee una operación llamada getOrders
para permitir este caso de uso. En la solución original diseñada por Jim Webber, el
barista debe ser capaz de acceder a la lista de pedidos en formato Atom feed20 en
tiempo real. Entonces en nuestro caso vamos a configurar el ESB para que nuestra
API manipule el mensaje y realice la transformación desde SOAP a Atom.
Empezamos definiendo la API, que llamaremos StarbucksOrderListAPI. Esta API esta
enlazada con el contexto “/orders”, esta API consiste en un único resource que maneja
las peticiones GET. <api name="StarbucksOrderListAPI" context="/orders"> <resource methods="GET" faultSequence="StarbucksFault"> […] </resource> </api>
El resource simplemente contactará con el servicio OMS del back-end para recuperar
la lista de pedidos pendientes en formato SOAP, entonces en la secuencia out- 20 http://tools.ietf.org/html/rfc4287 y http://tools.ietf.org/html/rfc5023
28
Por Juanjo Hernández IT Consultant
sequence aplicamos una transformación XSLT para convertir la respuesta SOAP en
un formato Atom válido.
<xslt key="OrderFeedGenerator"> <property name="SystemDate" expression='get-property("SYSTEM_DATE", "yyyy-MM-dd'T'hh:mm:ss'Z'")'/> <property name="SystemURL" expression="$ctx:STARBUCKS_SYSTEM_URL"/> </xslt>
No obstante, cambiar el formato del mensaje no es suficiente. A no ser que enviemos
una cabecera content-type adecuada con la respuesta, el cliente que ha realizado la
llamada no recocerá la respuesta como una respuesta correcta de tipo Atom feed, por
lo tanto tenemos que añadir el siguiente mediador property a la configuración que
supervisará que la respuesta devuelta tenga el content type “application/atom+xml”.
<property name="ContentType" value="application/atom+xml" scope="axis2"/>
Ahora probamos la api con el siguiente commando Curl
curl -v http://localhost:8281/orders
Si con anterioridad se han enviado nuevos pedidos al sistema OMS mediante la API
StarbucksOrderAPI, ahora deberíamos recibir una respuesta de nuestro Atom feed de
pedidos similar a la siguiente. Advertir que si se realiza la consulta en algunos
navegadores como Internet Explorer que llevan incorporados lectores Atom, la
visualización de la respuesta será formateada para una visualización más agradable.
29
Por Juanjo Hernández IT Consultant
Ilustración 9. Internet Explorer con lector Atom.
5.6. Revisando el estado del pago.
Antes de que el Barista pueda entregar la bebida, debe supervisar si el cliente ha
pagado por ella. Nuestra API StarbucksPaymentAPI ya permite esto, si la aplicación
de Barista conoce el identificador del pedido puede revisar el estado del pago del
pedido enviando una solicitud HTTP GET que corresponde al resource del pago.
curl -v http://localhost:8281/payment/order/order-id
La orden superior devuelve una respuesta 200 OK conjuntamente con la descripción
del pago si el cliente ha realizado el pago, en caso contrario el estado devuelto será un
404 Not Found
5.7. Eliminando pedidos completados de la lista.
La Aplicación de Barista debe eliminar los pedidos preparados de la lista de pedidos
pendientes, para que el mismo pedido no pueda ser procesado más de una vez. El
back-end de Starbucks posee una operación removeOrder que puede ser usada para
implementar este escenario. En un entorno RESTful, la manera apropiada de realizar
la acción de borrar un recurso es enviado una petición HTTP DELETE, entonces
definimos el siguiente resource en la API StarbucksBaristaAPI.
30
Por Juanjo Hernández IT Consultant
<resource uri-template="/order/{orderId}" methods="PUT DELETE">
Este resource aceptará las peticiones DELETE, invocará la operación removeOrder
del OMS y eliminará el pedido especificado de la lista. Observar el uso de la plantilla
URI “/orders/{orderId}”. De esta forma la aplicación barista puede invocar el mismo
resource especificando diferentes identificadores de pedidos. En la in-sequence del
resource construimos un mensaje que la operación removeOrder SOAP entienda
usando el valor de la variable orderId de la plantilla.
Para probar esta acción puedes ejecutar el siguiente comando Curl. Cómo en todos
los ejemplos anteriores se debe sustituir la cadena order-id por el identificador real del
pedido. curl -v -X DELETE http://localhost:8281/barista/order/order-id
La respuesta debería ser similar a la siguiente: HTTP/1.1 200 OK Content-Type: application/xml; charset=UTF-8 Server: WSO2 Carbon Server Date: Wed, 15 Aug 2012 13:38:20 GMT Transfer-Encoding: chunked Connection: Keep-Alive <?xml version="1.0" encoding="UTF-8"?> <message xmlns="http://starbucks.example.org">Order deleted</message>
Para asegurar que el pedido ha sido eliminado de la lista, podemos enviar una petición
GET a la API StarbucksOrderListAPI y comprobar en la lista de pedidos pendientes
que es devuelta en formato Atom feed, que ya no se encuentra el pedido eliminado.
6. Mejorando la Solución en general.
Hasta el momento tenemos en las manos una solución prácticamente completa.
Ambos flujos (workflows) descritos en el artículo de Jim Webber han sido
implementados satisfactoriamente. Pese a todo existen multitud de mejoras a hacer.
En esta sección vamos a echar un vistazo a cómo ciertos requisitos no funcionales
como la seguridad, la usabilidad o el rendimiento que pueden ser incorporados a la
solución tan solo con ser implementados sobre el ESB.
31
Por Juanjo Hernández IT Consultant
6.1. Negociando el formato del contenido (Content Negociation).
Content Negociation es el mecanismo por el cual un cliente y un servidor se
comunican uno con otro y deciden un formato de contenido a usar para la
transferencia de datos. En nuestra solución hasta el momento, hemos utilizado XML
(application/xml) como el principal medio de transmisión de datos. Sin embargo
podríamos utilizar otros formatos para la transferencia de contenidos, como texto plano
o JSON para lograr el mismo resultado y adaptarse a cualquier dispositivo.
Las aplicaciones cliente en el mundo real tienen sus propios content types preferidos.
Por ejemplo un navegador web normalmente prefiere recibir la información en HTML.
Una aplicación de escritorio basada en Java21 habitualmente prefiere POX, en cambio
un app móvil usualmente preferirá JSON. Para mantener la interoperabilidad, las
aplicaciones servidor (server side) deberían estar preparadas para servir el contenido
usando cualquiera de esos formatos. Con el uso del mecanismo de negociación de
contenidos (Content Negociation) el cliente puede indicar su preferencia de tipo de
contenido al servidor y el servidor puede servir las peticiones usando el Content Type
del cliente.
La especificación HTTP proporciona los elementos básicos para construir poderosos
frameworks de content negotiation. Un cliente HTTP puede indicar las preferencias de
su content type enviando en el mensaje una cabecera Accept en las peticiones. El
cliente puede indicar cero, uno o más tipos de contenidos aceptados. Cuando no se
especifica el tipo de contenido preferido, el servidor de manera predeterminada, usa
uno de los tipos de contenido admitidos.
Vamos a ver cómo podemos añadir una negociación de contenidos básica en nuestra
solución. En este ejemplo no vamos a pararnos en las preferencias enviadas por el
cliente, más bien vamos a definir nuestro propio orden de prioridad para el manejo de
múltiples preferencias. Usaremos el API StarbucksOrderListAPI como conejillo de
indias. Primero necesitamos recuperar el valor de la cabecera Accept enviada por el
cliente, hacemos esto con el siguiente mediador property en la in-sequence como
sigue:
21 http://www.java.com/en/
32
Por Juanjo Hernández IT Consultant
<property name="STARBUCKS_ACCEPT" expression="$trp:Accept"/>
Ahora en la out-sequence haremos algunas evaluaciones para determinar qué tipo de
content type se usa para enviar la respuesta:
<switch source="$ctx:STARBUCKS_ACCEPT"> <case regex=".*atom.*"> […] </case> <case regex=".*text/html.*"> […] </case> <case regex=".*json.*"> […] </case> <case regex=".*application/xml.*"> […] </case> <default> […] </default> </switch>
La configuración anterior determina la siguiente orden de prioridad de los tipos de
contenidos:
1. Atom (también usado por defecto)
2. HTML
3. JSON
4. POX
Para probar la negociación de contenidos debemos añadir en nuestros comandos Curl
el contenido aceptado por el cliente y por lo tanto cómo queremos recibir la respuesta
tal como sigue:
curl -v http://localhost:8281/orders curl -v -H "Accept: application/xml" http://localhost:8281/orders curl -v -H "Accept: application/json" http://localhost:8281/orders curl -v -H "Accept: text/html" http://localhost:8281/orders
6.2. Aplicando Seguridad al API
Hasta el momento nuestra API StarbucksPaymentAPI está expuesta sobre HTTP sin
ninguna forma de autenticación, pero eso no es como se diseñan los sistemas de pago
en el mundo real. Debemos restringir el acceso a esta API usando HTTPS e
idealmente debemos introducir alguna forma de autenticación en la ecuación.
33
Por Juanjo Hernández IT Consultant
Restringir la API de pago usando el protocolo HTTPS es sencillo. Simplemente se
debe añadir el protocol=”https”22 en la definición del resource, e inmediatamente el
resource estará accesible sobre HTTPS, de la siguiente manera:
<resource uri-template="/order/{orderId}" methods="GET PUT" faultSequence="StarbucksFault" protocol="https">
Ahora cuando accedemos con la siguiente orden Curl a la API de pago usando el
protocolo HTTP:
curl -v -X PUT-d @payment.xml -H "Content-type: application/xml" http://localhost:8281/payment/order/order-id
El mensaje no será enviado ni siquiera al resource. Será remitido a la secuencia
principal del ESB la cual está configurada para devolver una respuesta 403,
obtendremos algo similar a:
HTTP/1.1 403 Forbidden Content-Type: application/xml; charset=UTF-8 Host: 127.0.0.1:8281 Date: Wed, 15 Aug 2012 14:18:28 GMT Server: Synapse-HttpComponents-NIO Transfer-Encoding: chunked Connection: Keep-Alive <?xml version="1.0" encoding="UTF-8"?> <error xmlns="http://ws.apache.org/ns/synapse">Invalid request</error>
El único modo de poder acceder ahora a la API StarbucksPaymentAPI es vía HTTPS.
Invocando el siguiente comando Curl como sigue, y obtendremos una respuesta 201.
curl -v -X PUT -d @payment.xml -H "Content-type: application/xml" -k -X PUT https://localhost:8244/payment/order/order-id
Ahora que ya tenemos algo de seguridad a nivel de transporte implementado en
nuestra API de pago, vamos a ver como añadir lógica de autenticación a la solución.
Las APIs REST en WSO2 ESB permiten un concepto llamado handler. Uno o más
handler pueden participar en una API donde interceptan los flujos de mensajes y
añadir funcionalidades de QoS (Calidad de servicio). Para el propósito de este artículo
22 Existe un inconveniente si se realiza esta acción directamente desde el editor de la API de la consola de Administración, si se introduce protocol=”https” al resource y se guarda, la acción se realiza correctamente, pero al volver a abrir el editor manual de la API la palabra https se sustituye por un 2, es decir protocol=”2”, lo cual lanza un error al intentar guardar.
34
Por Juanjo Hernández IT Consultant
vamos a usar un handler personalizado que proporciona la funcionalidad de HTTP
Basic Authentication.
Apagamos el servidor de WSO2 ESB si está arrancado. Descargaremos el jar WSO2-
REST-BasicAuth-Handler-1.0-SNAPSHOT.jar23 que implementa la autenticación
anteriormente mencionada y lo copiamos en el directorio repository/components/lib,
posteriormente añadimos la definición del handler en la API de pagos
StarbucksPaymentAPI:
<handlers> <handler class="org.wso2.rest.BasicAuthHandler"/> </handlers>
Cuando reiniciamos el ESB, la API StarbucksPaymentAPI ya estará asegurada con la
HTTP Basic Authentication. Si intentamos acceder como anteriormente el resultado
será una respuesta con estado HTTP 401 Unauthorized, con un mensaje similar al
siguiente:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Basic realm="WSO2 ESB" Date: Wed, 15 Aug 2012 14:35:25 GMT Server: Synapse-HttpComponents-NIO Transfer-Encoding: chunked
Para que la API fuera accesible se deberían pasar las credenciales al comando Curl
de la siguiente manera (usuario: admin, contraseña: admin):
curl -v -k -H "Authorization: Basic YWRtaW46YWRtaW4=" https://localhost:8244/payment/order/order-id
Otra prueba que podemos intentar para comprobar la autenticación es acceder a la
API mediante un navegador web. El navegador mostrará un dialogo estándar de
autenticación para que se puedan introducir las credenciales:
23 https://www.dropbox.com/s/1n57ta3pz0xkamz/WSO2-REST-BasicAuth-Handler-1.0-SNAPSHOT.jar
35
Por Juanjo Hernández IT Consultant
Ilustración 10. Dialogo estándar de autenticación en el navegador.
Existen otras maneras de habilitar autenticación en una API o Proxy Service en el ESB
sin usar la propiedad handler, en algunos casos es posible habilitar la autenticación
con mediadores property de alguna forma similar a la siguiente:
<property xmlns:sb=http://www.starbucks.com name="OB_USR" expression="//sb:Starbucks/*[1]/usr" type="STRING"/> <property xmlns:ob=http://www.openbravo.com name="OB_PWD" expression="//sb:Starbucks/*[1]/pwd" type="STRING"/> <property name="OB_CHAR1" value=":" type="STRING"/> <property name="OB_B64CREDENTIALS" expression="fn:base64Encode(concat($ctx:OB_USR, $ctx:OB_CHAR1,$ctx:OB_PWD))"/> <property name="Authorization" expression="fn:concat('Basic',$ctx:OB_B64CREDENTIALS)" scope="transport"/>
Es el mismo mediador el que gestiona la autenticación y un mediador property llamado
Authorization se encarga de la autenticación del mensaje en el servidor.
6.3. Mejorando el rendimiento (Caching).
El tiempo de respuesta en algunas llamadas a la API puede ser significativamente
mejorada si se introduce alguna característica de cacheado en nuestra solución. Jim
Webber en su artículo ya sugiere que la lista de pedidos pendientes debería estar
cacheado para que la generación del feed Atom no sobrecargue los servidores del
36
Por Juanjo Hernández IT Consultant
back-end OMS. Podemos implementar esto en WSO2 ESB usando el mediador cache.
El mediador cache almacena las respuestas en una cache de memoria con la clave de
la lista de llamadas DOM. Así mismo si la misma llamada es enviada varias veces,
solo la primera invocación será mediada por el servicio del back-end. Todas las
llamadas subsiguientes serán servidas por la caché. El siguiente ejemplo muestra
como el mediador cache puede ser configurado en una de las secuencias del ESB:
<cache timeout="20" scope="per-host" collector="false" hashGenerator="org.wso2.caching.digest.DOMHASHGenerator"> <implementation type="memory" maxSize="100"/> </cache>
El tiempo de respuestas a cachear y el tiempo de duración de la misma es
configurable en la sentencia anterior con los parámetros maxSize y timeout.
7. Ejecución de la Solución.
7.1. Clientes GUI.
Ahora que la integración está completa, podemos trabajar con algunas aplicaciones UI
que consumen las API REST, estas aplicaciones en el mundo real podrían ser:
• Website.
• Aplicación de escritorio “standalone”.
• Aplicación móvil.
En este punto podemos descargarnos la aplicación starbucks-rest-sample.ja24r y la
ejecutamos con la orden java –jar Starbucks-rest-sample.jar podremos probar todo lo
que aparece en este artículo de una manera gráfica:
24 https://svn.wso2.org/repos/wso2/people/hiranya/rest-sample/bin/starbucks-rest-sample.jar
37
Por Juanjo Hernández IT Consultant
Ilustración 11. Aplicación Starbucks Cliente.
Ilustración 12. Aplicación Starbucks Barista.
38
Por Juanjo Hernández IT Consultant
8. Problemas encontrados en la implementación original.
Al realizar el ejemplo del artículo orginal de Mr. Hiranya Jayathilaka, encontramos
algunos problemas que ya hemos ido comentando a lo largo del presente documento,
hay otras que no he tenido oportunidad de comentar pero que es interesante dejarlo
aquí explicado con el fin de no perder tiempo en estos detalles:
• El importe de las bebidas se manipula con un doble y se manipula en los
clientes con Formatter, pues bien este Formatter usa como sistema de
puntuación decimal el punto, cómo es habitual en el mundo anglosajón, en
cambio, cómo sabemos, la separación decimal en el mundo latino es la coma,
se debe revisar pues los clientes GUI para que realicen la manipulación
correctamente. Para adaptarlo a cada país concreto debes conocer cuáles son
los países que utilizan el punto y cuales la coma para separar los decimales25
• El tag handler para realizar la autenticación tal y como se comenta en el
capítulo correspondiente “desaparece” al editar la API, aunque si se edita la
API directamente en su fichero XML el ESB lo gestiona y funciona como es
esperado.
9. Conclusiones.
• Es evidente que durante la creación de una completa solución usando el stack
de WSO2, hemos aprendido cosas importantes como REST, API, SOAP,
HATEOAS y el uso de WSO2 ESB para abordar este tipo de solución.
• En la vida real, el empleo de WSO2 ESB deja abierta la posibilidad de mejorar
la solución en todos los niveles: rendimiento, escalabilidad y seguridad.
• Se ven posibilidad de mejora importante conforme se va conociendo la
tecnología, en los siguientes documentos incluiremos mejoras como:
o Uso de WSO2 Business Activity Monitor.
o Seguridad usando WSO2 Identity Server.
o Uso de WSO2 API Manager para gestionar nuestra API.
o Desarrollo de una nueva Aplicación Cliente Mobile (responsive design).
25 http://es.wikipedia.org/wiki/Separador_decimal
39
Por Juanjo Hernández IT Consultant
A. Referencias y recursos
• WSO2 AS: http://wso2.com/products/application-server/
• WSO2 ESB: http://wso2.com/products/enterprise-service-bus/
• Artículo “How to GET a Cup of Coffee” de Jim Webber:
http://www.infoq.com/articles/webber-rest-workflow/
• Jim Webber Blog: http://jimwebber.org/
• Artículo “How to GET a Cup of Coffee the WSO2 Way” de Hiranya Jayathilaka:
http://wso2.com/library/articles/2012/09/get-cup-coffee-wso2-way/
• Recursos actualizados para desplegar y ejecutar este ejemplo:
o Archivos configuración API:
https://www.dropbox.com/sh/7el21wipkf6fvne/uxJ5NhY5Ec
o Archivos configuración endpoints:
https://www.dropbox.com/sh/ldlq6rsweb7z7yt/K8OA4GuY9E
o Archivos configuración local-entries:
https://www.dropbox.com/sh/qdoednc843aulzz/hK__izxkng
o Archivos configuración sequences:
https://www.dropbox.com/sh/mszkhj06gn4206b/KQ3VMBfVID
o Archivo jar para Basic Authentication:
o https://www.dropbox.com/s/1n57ta3pz0xkamz/WSO2-REST-BasicAuth-
Handler-1.0-SNAPSHOT.jar
o Archivo aar para creación servicios SOAP OMS Starbucks:
o https://www.dropbox.com/s/b3leesblvkm2eyg/StarbucksOutletService.aar
B. Tabla de imágenes
Ilustración 1. REST TO SOAP Mediación ESB .............................................................. 6
Ilustración 2. Transformación de mensajes REST con ESB .......................................... 6
Ilustración 3. Configuración de contextos y plantillas de URL en resource. ................... 8
Ilustración 4. Solución de arquitectura de alto nivel. .................................................... 13
40
Por Juanjo Hernández IT Consultant
Ilustración 5. Solución de arquitectura con ESB. ......................................................... 14
Ilustración 6. Añadir Servicios Starbucks back-end en WSO2 AS. .............................. 15
Ilustración 7. Lista de servicios desplegados. .............................................................. 16
Ilustración 8. Detalle del servicio Starbucks OMS desplegado. ................................... 16
Ilustración 9. Internet Explorer con lector Atom. ........................................................... 29
Ilustración 10. Dialogo estándar de autenticación en el navegador. ............................ 35
Ilustración 11. Aplicación Starbucks Cliente. ................................................................ 37
Ilustración 12. Aplicación Starbucks Barista. ................................................................ 37
41
Por Juanjo Hernández IT Consultant
Autor: Juanjo Hernández IT Consultant LinkedIn: https://www.linkedin.com/in/juanjohernandez Twitter: @JuanjoHC Chakray Consulting S.L. www.chakray.com
Revisado por: Luis Peñarrubia IT Consultant Roger Carhuatocto IT Consultant
Top Related