Feeds:
Entradas
Comentarios

Archive for the ‘WebServices’ Category


Hola a todos, como os prometí seguimos avanzando con el tema de REST. En el anterior post vimos como definir operaciones de negocio siguiendo la filosofía REST y también tuvimos un pequeño guiño al mundo BPM. Ahora toca ver como conseguir sistemas autodescriptivos y automatizados al estilo REST.

Por dejar las cosas claras, cuando hablo de autodescripción y automatización de un sistema me refiero a lo siguiente:

  • Dado un único punto de entrada al sistema, conocido por el consumidor de servicios, dicho consumidor debe ser capaz de descubrir todos los servicios ofrecidos por el sistema remoto de forma automática.
  • Dado un servicio conocido por un consumidor, dicho consumidor debe ser capaz de obtener información sobre como se usa dicho servicio de forma automática.
  • Conseguir los dos puntos anteriores con el menor nivel de acoplamiento posible entre proveedor y consumidor de servicios. De esta forma ambas partes sólo se deben poner de acuerdo en un conjunto de protocolos y formatos mínimos.
  • Un consumidor dotado de las capacidades anteriores debería ser capaz de interoperar con varios sistemas que pertenezcan al mismo dominio de negocio, aunque dichos sistemas hayan sido implementados por organizaciones diferentes.

Como vemos, básicamente se trata de conseguir un protocolo mínimo que permita crear clientes genéricos que puedan consumir cualquier servicio REST de un sistema, o incluso de sistemas distintos, que sigan el mismo protocolo. Se pretende un nivel de interoperabilidad a nivel global dentro del mismo dominio de aplicación. En fin, los objetivos son bastante ambiciosos y no son nada sencillos. ¿Existe algún sistema que los haya conseguido? ¿Un sistema que haya conseguido aquello que SOAP y WS-* con todas sus toneladas de documentación y protocolos no ha conseguido? Obviamente sí, la web. Veamos en que consiste en realidad la web:

  • Un conjunto de páginas o documentos accesibles mediante protocolo HTTP. Cada una tiene su propia URI.
  • Cada página se representa en un formato, o tipo mime, estandarizado, que es procesable por cualquier aplicación cliente. Este formato es HTML.
  • Cada una estas páginas están interrelacionadas entre ellas mediante enlaces. Al acceder a una página, tenemos la capacidad de navegar por los enlaces para acceder a otras. Esta propiedad es la que da sentido al término «web».
  • Dichos enlaces están anotados para que sepamos que información nos van a proporcionar dicha página. De esta manera, no necesitamos navegar a ciegas, consultando las anotaciones de cada enlace podemos saber que tipo de información podemos esperar si navegamos a dicha página. En HTML las anotaciones son descripciones textuales de los enlaces, que se pueden poner dentro de éstos y/o como atributos.
  • Un buscador donde buscar las páginas web que nos interesen. O sea, el google, yahoo, etc.

Con estas premisas, puedes usar un único cliente, el browser o navegador web, para consumir todas las páginas de la web que te interesen. ¿A que te parecería extraño que tuvieras que usar un cliente diferente para acceder a las páginas de empresas distintas? A esto señores, se le llama interoperabilidad y el resto son tonterías. Obviamente se puede romper la interoperabilidad si una página usa algo no estándar, pues esa funcionalidad no estándar no se verá con todos los navegadores. De ahí la importancia de hacer páginas web siguiendo los estándares, y olvidarse de eso de «esta web sólo funciona con IE». Otro consumidor de páginas web, menos conocido, pero igual de importante, son los bots, arañas o crawlers, usados por los motores de búsqueda e indexadores de la web. Como se ve sólo necesitamos el concepto de enlaces y estandarización a nivel de formatos para alcanzar interoperabilidad.

¿Cómo debemos diseñar nuestros servicios REST para obtener todas éstas ventajas? Obviamente tenemos que tomar ejemplo de la web y diseñar nuestros servicios REST como si fueran páginas web. Ya hemos visto el patrón comando y el entidad/colección, pero eso no basta, debemos estructurar nuestros servicios como si fueran una web de servicios interrelacionados. Al igual que las páginas web, nuestros servicios deben mantener links entre ellos, links que estén anotados de alguna manera estandarizada y los interconecten para obtener una red semántica. Pero esto por si mismo no basta, ya que tenemos que usar tipos mime estandarizados que sean interpretables por el mayor número de clientes. En definitiva, si queremos obtener un nivel de autodescripción y automatización similar al de la web debemos seguir los siguientes principios de diseño:

  • No tener servicios aislados, sino interrelacionarlos mediante enlaces.
  • Proporcionar un servicio central, o home, que nos proporcione un catálogo con enlaces, debidamente anotados, a todos los servicios ofrecidos por el sistema. Alternativamente se puede proporcionar un buscador usando el patrón colección.
  • Soportar un conjunto de tipos mime estandarizados, de forma que los clientes puedan procesar las representaciones de los recursos REST de forma sencilla. Cuanto más estándar sea el tipo mime mejor. Si es totalmente estándar como por ejemplo, application/xml, application/json o application/atom+xml, obtendremos un grado de interoperabilidad máximo. Otra opción usar tipos mime especializados en nuestro dominio de aplicación, con lo que el grado de interoperabilidad es menor, o incluso bajar nuestros requisitos de interoperabilidad y usar tipos mime únicos de nuestro sistema. Lo importante es que todos los clientes que deban acceder a nuestro sistema deben saber interpretar el tipo mime. Otra ventaja de usar tipos mime estándar es que nos encontraremos con librerías que sepan interpretarlos, con lo que nuestros desarrollos se simplificarán enormemente.
  • Dentro del marco de los tipos mime a usar, ser capaz de definir anotaciones para los enlaces, que describan la acción o la información que se llevará a cabo si se usa el recurso enlazado.

Si nuestro sistema REST sigue éstas buenas prácticas, entonces podemos calificarlo como un sistema HATEOAS. Esto del HATEOAS es una sigla que se expande en la hermosa oración: Hypermedia As The Engine Of Application State. HATEOAS es el último «palabro» de moda, que está en boca de todos los gurús del REST para deslumbrar a las masas. Es una de las siglas más horrendas de la historia de la informática que alguien acuñó para referirse simplemente a un sistema REST que cumpla con todos los puntos expuestos anteriormente. Muchos gurus consideran que los verdaderos sistemas REST son aquellos que son HATEOAS, con lo que si no sigues los principios HATEOAS tu sistema es sólo pseudorest. Tal vez inventaron esta sigla para distinguir al verdadero REST del falso, pero yo no me meto en estas discusiones teológicas.

Como todo esto está quedando muy abstracto veámoslo con un ejemplo. Volvamos al caso de la tienda de libros, lo primero que debemos tener es un punto de entrada al sistema REST. A este punto de entrada le llamaremos servicio home. De esta manera nuestro cliente sólo necesita saber la URI de la home de nuestro sistema. ¿Qué información mantiene la home? Pues un conjunto de enlaces a los servicios que podemos usar en nuestro sistema. Veamos un ejemplo en JSON, de lo que devolvería el servidor si accedemos a la URI https://www.books.com/store/home:

[
  { 'rel':'bookServices', 'href':'/store/bookSrv', 'method':'GET' },
  { 'rel':'customerServices', 'href':'/store/customerSrv', 'method':'GET' },
  { 'rel':'orderServices', 'href':'/store/orders', 'method':'GET' }
]

Como vemos el documento devuelto contiene enlaces relativos, a los servicios REST que nos pueden servir para operar con libros, clientes, o pedidos. Nótese el uso del campo «rel» para anotar los enlaces. De esta forma un cliente automatizado que deseara acceder a los servicios de libros, buscaría en el recurso home el enlace con el campo rel con valor «bookServices». También es curioso el campo «method» que indica el método HTTP a usar para acceder a la URI. En este caso lo pongo por motivos didácticos, pero nos lo podemos ahorrar si el método a usar para realizar acción la acción es GET, que consideramos el método por defecto para navegar por un enlace. De esta manera el cliente REST no necesita saber las direcciones de todos los servicios expuestos en el servidor, ni que método HTTP usar en cada caso, sino sólo la URI del home, con el consiguiente desacoplamiento entre el cliente y el servidor. También debéis pensar que si cambiamos las URIs constructivas y entendibles que he puesto por otras URIs indescifrables, el sistema va a seguir funcionando. En este sentido, si la respuesta hubiera sido:

[
  { 'rel':'bookServices', 'href':'/store/wñu84v-4gsg', 'method':'GET' },
  { 'rel':'customerServices', 'href':'/store/uri-incognoscible', 'method':'GET' },
  { 'rel':'orderServices', 'href':'/store/ticket39caj2sv', 'method':'GET' }
]

Nuestro cliente no debería tener problemas para seguir funcionando. Estas URIs indescifrables se conocen como URIs opacas. Muchos gurus de REST mantienen un debate sobre si es mejor las URIs constructivas o las opacas. Los partidarios de las últimas sostienen que el uso de URIs opacas fuerza a los desarrolladores a diseñar sistemas HATEOAS. Yo prefiero las URIs constructivas, pero como hemos visto, si seguimos los preceptos de HATEOAS, ambas son intercambiables.

Volviendo al tema del acoplamiento, nos podemos fijar en las fuentes de acoplamiento que genera HATEOAS:

  • El formato a usar (en el ejemplo) es application/json. Ambas partes deben conocerlo. Sí, es un punto de acoplamiento, pero no conozco ningún sistema que pueda interoperar sin conocer el formato de los mensajes que intercambian. Obviamente como es REST podemos soportar varios formatos, tanto en el cliente como en el servidor, pero sólo es necesario que uno de dichos formatos sea común a ambos, con lo que la probabilidad de interoperar es mayor.
  • Que los enlaces están modelados con un subobjeto con tres campos: href, rel y method. El cliente y el servidor tienen que ponerse de acuerdo en cómo se usa cada campo y qué significa cada uno. Tal vez si hubiéramos usado otro tipo mime que soportara enlaces de forma nativa, esta información no sería necesaria.
  • Los valores de «rel» y lo que significan. Tenemos que tener un documento indicando que «bookServices» es el servicio de libros y «orderServices» para pedidos. Este nivel de acoplamiento puede ser similar al de conocer las URIs del servidor, pero nos da más flexibilidad. Son identificadores lógicos, parte del negocio y permiten desacoplarnos de las URIs usadas por el servidor, que pueden cambiar.

Como vemos el nivel de acoplamiento no es muy alto. ¿Qué ocurre si hacemos un GET sobre la URI https://www.books.com/store/bookSrv? De nuevo recibiremos un documento JSON:

{
  'home': {
    'description':'go to system home',
    'href':'/store/home'
  }, 'search': {
    'description':'search books',
    'href':'/store/books{?title,author,minPrice,maxPrice}'
  }, 'searchForm': {
    'description':'search books using a form',
    'href':'/store/books/searchForm'
  }, 'byId': {
    'description':'get book by id',
    'href':'/store/books/{bookId}'
  }, 'create': {
    'description':'create a new book',
    'href':'/store/books',
    'method':'POST'
  }, 'createForm': {
    'description':'create a new book using a form',
    'href':'/store/books/createForm'
  }, 'delete': {
    'description':'delete all books matching criteria',
    'href':'/store/books{?title,author,minPrice,maxPrice}',
    'method':'DELETE'
  }, 'search': {
    'description':'delete books using a form',
    'href':'/store/books/deleteForm'
  }
}

Mirad los enlaces, esta vez no uso los campos «rel», los he sustituido por el nombre que tienen dichos enlaces dentro del documento que los contiene. Esto lo puedo hacer porque estamos usando JSON, si fuera HTML tendría que conservar los campos «rel». Además he añadido un campo «description» a cada enlace, por si hay que generar una UI a un humano a partir de este documento. También he omitido el campo «method» en cuyo caso se toma como valor por defecto «GET». Además, si os fijáis, los nombre de los enlaces representan ahora operaciones, no recursos. Comentemos un poco más en detalle:

  • El enlace «search» no tiene una verdadera URI. Sino una URI Template. Las URI Template son un estándar de facto para definir URIs paramétricas y  nos indican como construir una URI en función de una serie de parámetros. En el caso que tenemos entre mano, nos indica como hacer búsquedas de libros y por que campos. En el ejemplo podemos usar los parámetros title, author, minPrice y maxPrice, que se adjuntan a la query string. Podéis encontrar más información sobre las URI Template aquí.
  • El enlace «byId» indica como acceder a una entidad. De nuevo usa la sintaxis URI Template.
  • Obviamente los enlaces «create» y «delete» para crear y borrar usando el patrón colección/entidad. Fijaros que en el caso de «delete» no se especifica ningún parámetro. Para crear un libro podemos usar el patrón colección/entidad y mandar en el cuerpo del POST el estado inicial del libro. En este caso el cliente y el servidor deben de estar acoplados en el sentido de que ambos deben estar de acuerdo sobre como se representa el estado de un libro mediante un documento JSON. Sin embargo veremos más adelante otra forma diferente de hacerlo.
  • La idea del enlace «searchForm» es dar una alternativa a la búsqueda mediante URI Template. En este caso usamos lo que yo llamo un patrón formulario. Consiste en que el servidor devuelve un formulario, el cliente rellena sus campos y lo devuelve a la URI apuntada por el enlace «submit». Los campos del formulario se pueden devolver en la query string, en caso de que el submit use GET o en el body.
  • De forma equivalente tenemos «createForm» y «deleteForm». Si accedemos a estos «formularios» vemos que estamos de nuevo ante un patrón formulario para crear y otro para borrar. Para ejecutar la operación tenemos que navegar por el enlace «submit». En el body de la petición hemos de mandar el formulario que hemos recibido pero con los campos rellenos. Veamos un ejemplo de lo que devuelve el servidor si hacemos un GET a la URI indicada por deleteForm:
    {
      'title': { // Definición campo de búsqueda
        'description':'title to match', // Descripción campo
        'value':'' // Valor inicial del campo
      }, 'minPrice': {
        'description':'minimum price',
        'value':0
      }, 'maxPrice': {
        'description':'maximum price',
        'value':0
      }, 'authors': {
        'description':'comma separated author list',
        'value':''
      }, 'execute': {
        'description':'set this field to true to execute operation or to false if you want to execute it later',
        'value':true
      }, 'submit': {
        /* Modo de empleo:
          * Usar este enlace para mandar el formulario
          * Simplemente devolver el formulario relleno a
          * la URI indicada usando el método PUT
          */
        'description':'create a new book',
        'href':'/store/books/deleteCommands/ticket-DFSG2QA3F9',
        'method':'PUT'
      }
    }

    Notad, que en este caso, el enlace «submit» apunta a un comando que usa el patrón ticket descrito en mi anterior artículo.

  • Finalmente fíjense en el enlace que nos devuelve de nuevo a la home. Como dije anteriormente los recursos deben estar interrelacionados. Tal vez el cliente llegó al sistema directamente por la URI del servicio de libros y no por la home.

Vemos que HATEOAS introduce otro pequeño grado de acoplamiento. O bien ambas partes saben manejar URI Templates, o bien ambas partes conocen el patrón «formulario». Sin embargo merece la pena, ya que tanto las URI Templates como el patrón formulario son mecanismos muy flexibles y genéricos y nos permiten autodescribir y automatizar multitud de operaciones. Normalmente se usan URI Templates para hacer consultas y operaciones sencillas, y formularios para operaciones «create» o el autodescubrimiento de comandos.

Continuemos con más ejemplos: ¿cómo sería una entidad que representara a un libro siguiendo el enfoque HATEOAS?

{
  'update': {
    'description':'update book data (collection/entity style)',
    'href':'/store/books/AS-d25RWV',
    'method':'PUT'
  }, 'delete': {
    'description':'delete book (collection/entity style)',
    'href':'/store/books/AS-d25RWV',
    'method':'DELETE'
  }, 'order': {
    'description':'order this book',
    'href':'/store/orders/ticket-sfsd35h-5qdt{?bookId=AS-d25RWV,amount,isGift,execute,customerId}',
    'method':'PUT'
  }, 'orderForm': {
    'description':'order this book using a form',
    'href':'/store/orderForm?ticket=ticket-sfsd35h-5qdt&bookId=AS-d25RWV',
    'method':'PUT'
  }
  /* Fields with book data not shown*/
}

Vemos que modelamos las operaciones «update» y «delete» mediante enlaces, ¿es esto necesario? Recordad que sabemos que update se hace con PUT sobre la URI de la entidad y delete se hace con DELETE sobre la URI de la entidad, sólo porque estamos aplicando el patrón colección/entidad. Tal vez nuestro cliente no haya sido diseñado para seguir ese patrón y sólo sepa de HATEOAS. De hecho el seguir el patrón colección/entidad es un punto de acoplamiento extra, ya que ambas partes deben conocerlo. Esto es innecesario si seguimos el enfoque HATEOAS. Además ya hemos visto antes, con el uso de formularios de búsqueda, creación y borrado, formas alternativas de operar, que no siguen el patrón colección/entidad.

También podemos observar el enlace «order» para dar de alta un comando de comprar el libro. Observemos como la URI Template está parcialmente rellena, indicando por nosotros el identificador del libro que queremos comprar. De nuevo usamos un patrón comando con tickets, aunque saltándonos el paso de creación del ticket con POST. Para completar tenemos un enlace alternativo a un formulario, llamado «orderForm» que nos permite comprar usando un formulario. Veamos este último:

{
  'bookId': {
    'description':'Book to order',
    'value':'AS-d25RWV'
  }, 'amount': {
    'description':'Number of books to order',
    'value':1
  }, 'isGift': {
    'description':'maximum price',
    'value':0
  }, 'customerId': {
    'description':'The id of the customer who orders the book',
    'value':'',
    'findCustomerId': {
        'description':'Search a customer id',
        'href':'/store/customers{?name,surname,dni}'
    }
  }, 'execute': {
    'description':'set this field to true to execute operation or to false if you want to execute it later',
    'value':true
  }, 'submit': {
    'description':'order the book',
    'href':'/store/orders/ticket-sfsd35h-5qdt',
    'method':'PUT'
  }
}

Si seguimos el enlace «submit» ejecutamos el formulario mediante un PUT. Si os dais cuenta la operación es exactamente la misma que si hubiéramos seguido el enlace «order» del libro, un PUT sobre la misma URI. Observar el detalle del enlace dentro de «customerId» para poder localizar el identificador de un cliente por nombre, apellidos y dni.

Otra variante, que yo no he seguido, sobre como interconectar los formularios y los comandos, es que el formulario haga un POST para crear un ticket y que este POST devuelva el ticket del nuevo comando. Posteriormente se trabaja siguiendo de forma clásica el patrón comando descrito en mi anterior artículo. Lo mismo vale con las URI Template y los comandos. Este diseño es más simple y sencillo de entender, pero es un poco más ineficaz, ya que necesita un POST extra, mientras que en el enfoque ilustrado en los ejemplos anteriores el POST no es necesario, ya que el ticket viene creado con el formulario o el enlace. Vosotros decidís.

Podéis ver que un cliente HATEOAS no necesita conocer como modelamos nuestro sistema mediante patrones comando, entidad/colección y tickets. Simplemente inspecciona los documentos devueltos por el servidor, sigue los enlaces que le interesa, rellena URI Templates o manda formularios. Si planteamos nuestro servidor con HATEOAS éste es explorable y usable por cualquier cliente HATEOAS, con una parametrización mínima.

Realmente todo esto del HATEOAS es un tema muy interesante y creo firmemente que es el único camino conocido para conseguir interoperabilidad a gran escala. También es muy productivo a la hora de desarrollar consumidores de servicios, ya que mediante un pequeño framework podemos atacar a una gran variedad de servidores REST. Ahora podéis entender porque no me interesa para nada ni el WSDL ni el UDDI. WSDL simplemente describe a nivel sintáctico los mensajes, define las URIs de cada servicio y cómo se intercambian los mensajes. ¿Con que fin? Con el de generar toneladas de código que supuestamente implementen todo ese complejo protocolo. UDDI es supuestamente un servicio de directorio donde encontrar los servicios SOAP, pero no he visto a nadie usarlo a gran escala. Con HATEOAS no necesitamos nada de eso, ya que lo tenemos todo unificado en un mismo enfoque. Sólo necesitamos ponernos de acuerdo en unos pocos formatos de datos, la URI de un servicio home que actúa como directorio, y compartir unos pocos patrones de diseño. El resto es seguir los enlaces… y navegar por una web de servicios REST. Al fin y al cabo, lo que queríamos desde un principio eran servicios web.

Desgraciadamente, una gran mayoría de arquitectos web, entre los que me incluyo, llevamos tanto tiempo pensando en tablas de bases de datos y en servicios RPC procedurales, que este estilo no está siendo muy difundido de momento. La mayoría de los sistemas REST que me encuentro no son HATEOAS, y ni siquiera implementan el patrón comando. La mayorías de los servicios REST que conozco usan de forma sencilla el patrón colección/entidad, pero poco más, y con poca interconexión entre recursos REST. Tal vez al diseñar dichos servicios los desarrolladores estuvieran pensando en tablas SQL y no en web. Tal vez estuvieran usando Grails… uy lo que he dicho, tranquilos, es broma };-)

ACTUALIZACIÓN: Un lector me comenta por el twitter la existencia de un framework REST basado en principios HATEOAS. Podéis echarle un vistazo aquí por si os interesa. A primera vista usa XML, con etiquetas link con atributo rel para autodescubrimiento de recursos. Sobre cada recursos puedes usar POST/PUT/DELETE/GET siguiendo el patrón colección/entidad. Al menos es lo que he entendido por la documentación. Fijaros como un enfoque muy sencillo puede llevar a un sistema autodescubrible con facilidad. Es interesante ver que no usa el patrón comando, ya que me comentan que no lo consideran REST puro. No estoy de acuerdo con esto último. Sin embargo, el que exista un patrón no significa que lo tengamos que usar forzosamente. Es mejor seguir el principio KISS, y por lo tanto si no le ven aplicación es bueno que no lo hayan usado.

Read Full Post »


Hola a todos, hoy vuelvo de nuevo al ataque con REST. Vamos a empezar a diseñar cosas un poco más complejas. En el anterior post sobre REST vimos sobre todo como diseñar servicios REST que representaran el patrón colección/entidad. Me gustaría ahora centrarme en un punto importante: el modelado de operaciones de negocio.

Como sabéis, en REST, al contrario que en OO tradicional, no podemos extender de forma arbitraria el conjunto de operaciones que podemos ejecutar sobre un recurso u objeto. Así pues, ¿qué hacemos para modelar operaciones de negocio arbitrarias? Supongamos por ejemplo que tengo una tienda de libros, y tengo mi recurso REST que representa a mis libros, ¿cómo modelo la operación de vender un libro? Si estuviéramos en OO tradicional podríamos añadir un método vender(Cliente,Numero de unidades) a la interfaz de un supuesto objeto Libro, o quizás tuviéramos una interfaz IGestionLibreria donde añadir un método equivalente. Pero en REST no podemos añadir métodos, en concreto si usamos HTTP y el patrón colección/entidad, sólo tenemos las operaciones CRUD.

En estos casos la forma correcta de modelar la operación es aplicar el patrón comando. Para los que no conozcáis el patrón comando, éste consiste en transformar una operación en un objeto. En vez de crear un método «vender», creamos una clase «Vender». Cada vez que queramos ejecutar una operación «Vender», instanciamos dicha clase e invocamos su método «execute». En el constructor de ésta clase pasamos los parámetros que necesita la operación, a saber, el libro, el cliente y cuantas unidades queremos vender. Lo interesante del patrón comando es que podemos almacenar las instancias de las operaciones que hemos ejecutados, y mantener de esta manera un histórico de operaciones. Si a la clase que representa la operación le añadimos una operación «undo», podemos usar dicha operación y el histórico de comandos para implementar una cola de undo/redo, cosa bastante interesante.

¿Cómo aplicamos el patrón comando al mundo REST? Sencillo, cada vez que tengamos una operación de negocio que no encaje directamente en un CRUD sobre los recursos existentes, creamos un nuevo recurso que represente la ejecución de dicha operación. A esto, en algunos sitios, lo llaman proceso de reificación, yo simplemente lo llamaré el patrón comando aplicado a REST.

El proceso para ejecutar una venta, sería realizar una petición de creación de una entidad «venta» sobre la colección de ventas. Siguiendo el patrón colección/entidad:

  1. Petición, nótense los parámetros en el cuerpo. Para variar esta vez no usaré JSON:
    POST https://www.myshop.com/store/orders HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    Accept: application/x-www-form-urlencoded;application/json;charset=UTF-8;q=0.5
    
    bookId=Q1T52GVE3&customerId=juan34&numberOfBooks=1
    
  2. Respuesta ok.
    HTTP/1.1 201 Created
    Location: https://www.myshop.com/store/orders/RE356CWX
    
    

Esta es la forma más sencilla de implementar un comando en REST, hacemos un POST a la colección pertinente y pasamos los parámetros en el body. El sistema ejecuta el comando, lo registra en un histórico y nos devuelve la URI de la ejecución del comando. En los casos más sencillos, este histórico no necesita ser persistente y puede durar sólo durante la sesión de usuario, o durante un tiempo predeterminado. En otros casos nos interesa un histórico persistente, normalmente por motivos de auditoria y depuración.

Ahora pensemos un poco en la dura realidad, ¿qué ocurre si el servidor no me responde? Esto puede pasar por múltiples motivos: perdimos la conectividad, el mensaje de respuesta se perdió, el navegador se colgó, la respuesta tarda en llegar porque usamos un super ADSL de oferta, etc. Además el protocolo HTTP no asegura la entrega de mensajes. Ante este tipo de errores lo normal es reintentar, ¿cuántas veces hemos refrescado el navegador? Esto no es problema cuando usamos GET, PUT o DELETE, ya que son idempotentes. Pero hemos usado POST para modelar la ejecución del comando, si repetimos la petición, y resultó que la anterior se había realizado con éxito, pero nosotros no nos habíamos enterado, el comando se ejecutará por duplicado. En casos simples esto no importa, y la solución anterior es adecuada. En la mayoría de los casos esto no es así. No queremos que un cliente impaciente que no para de pulsar el botón de comprar reciba en casa veinte ejemplares de «Como se hizo te lo dije…».

La solución en este caso consiste en separar la ejecución del comando en dos fases: crear el comando en una petición y ejecutarlo en otra. Veámoslo:

  1. Petición de crear comando. Esta vez no enviamos parámetros, no es necesario con este diseño, pero podríamos si quisiéramos.
    POST https://www.myshop.com/store/orders HTTP/1.1
    Accept: application/x-www-form-urlencoded;application/json;charset=UTF-8;q=0.5
    
    
  2. Respuesta ok.
    HTTP/1.1 201 Created
    Location: https://www.myshop.com/store/orders/RE356CWX
    
    
  3. Rellenamos el comando con un PUT, mandando los parámetros en el body.
    PUT https://www.myshop.com/store/orders/RE356CWX HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    Accept: application/x-www-form-urlencoded;application/json;charset=UTF-8;q=0.5
    
    bookId=Q1T52GVE3&customerId=juan34&numberOfBooks=1
    
  4. Respuesta de parámetros actualizados con éxito.
    HTTP/1.1 204 No Content
    
    
  5. Finalmente ejecutamos el comando con otro PUT. Fijaos en el campo «execute» que lo ponemos a true. Asumimos que si un parámetro no está en la petición, simplemente no se modifica.
    PUT https://www.myshop.com/store/orders/RE356CWX HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    Accept: application/x-www-form-urlencoded;application/json;charset=UTF-8;q=0.5
    
    execute=true
    
  6. Respuesta de ejecución correcta:
    HTTP/1.1 204 No Content
    
    

Como vemos, este diseño nos ofrece mayor flexibilidad. Por un lado podemos repetir el paso 3 cuantas veces queramos, y así construir el comando poco a poco o cambiar de opinión sobre cuantos ejemplares de un libro queremos comprar. Este enfoque encaja muy bien con interfaces de usuario RIA en las que el usuario va «faciendo entuertos», que si compra, que si ahora no, etc. También podemos fusionar el paso 3 y el 5 en una única petición PUT, que contenga tanto los parámetros como el campo execute. Por ejemplo, el caso de que ahora el usuario quiera 5 ejemplares, y queramos ejecutar directamente el comando:

PUT https://www.myshop.com/store/orders/RE356CWX HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Accept: application/x-www-form-urlencoded;application/json;charset=UTF-8;q=0.5

bookId=Q1T52GVE3&customerId=juan34&numberOfBooks=5&execute=true

Por supuesto siempre podemos hacer un GET sobre la URI del comando para saber si está ejecutado o no y los parámetros con los que está configurado.

Otro punto interesante es que podemos extender este diseño del patrón comando, sustituyendo el campo binario «execute», por un campo «state» que admita varios valores. Si usamos el campo binario «execute» el comando o se ha ejecutado o no, pero a veces necesitamos poder representar estados de ejecución intermedios. Por ejemplo, un pedido a la tienda de libros, puede pasar por varios estados: «nuevo» cuando se ha creado pero no se ha formalizado, «pendiente» cuando hemos hecho la compra, «enviado» cuando todos los libros están disponibles y han sido enviados a nuestra dirección, «cerrado» cuando hemos recibido los libros y «cancelado» si nos arrepentimos. La idea es que el comando puede pasar de un estado a otro, aunque todas las transiciones no tienen por que ser válidas, y cada transición de un estado a otro puede tener efectos secundarios, como que me cobren cuando paso de «nuevo» a «pendiente» o que me devuelvan el dinero si paso a «cancelado». Que curioso, cambiando un campo binario, por un campo que admite varios estados, acabamos de modelar la interfaz REST de un BPM, transformar un simple comando en un proceso de negocio, ¿alguien da más? Que sea fácil implementar toda esta lógica en el servidor es otra cosa, pero desde el punto de vista del consumidor del recurso REST, la cosa está bastante sencilla.

¿Cómo implementamos una funcionalidad de undo/redo? En el primer enfoque, más simplista, basta con mandar una petición DELETE a la URI del comando que se ha creado. En el segundo enfoque podemos usar también DELETE, pero tenemos la opción de hacer un PUT mandando el campo «execute» a false. De la misma manera que el comando se ejecuta al cambiar «execute» de false a true, se produce una operación de deshacer cuando cambiamos dicho campo de true a false. La ventaja sobre usar DELETE es que no perdéis los datos que contenía el comando, con lo que podéis hacer un «redo». Si tenemos un proceso, en vez de un comando, no tiene mucho sentido usar esta metáfora de «undo/redo». Simplemente añadimos estados y transiciones apropiadas entre estos. Por ejemplo, para «deshacer» un pedido simplemente lo pasamos a estado «cancelado». Parece claro que podemos ir al estado «cancelado» desde los estados «nuevo», «pendiente» o «enviado». Si podemos pasar al estado «cancelado» desde el estado «cerrado», o no, depende de la lógica de negocio.

Una última cosa sobre el tema de «undo/redo», hasta ahora, usemos el enfoque de DELETE o el de cambiar el campo «execute», necesitamos la URI del comando. Esto permite al consumidor del cliente hacer «undo» sobre varios comandos en un orden independiente al que fueron ejecutados. Sin embargo, en muchos escenarios, los comandos se deben deshacer en orden inverso al que fueron ejecutados. En estos casos lo más fácil es usar una URI especial, que representa el último comando ejecutado. Por ejemplo: https://www.myshop.com/system/orders/last. Cualquier operación sobre esta URI, nos devuelve una redirección temporal, código HTTP «303 See Other», a la URI del último comando ejecutado.

Veamos ahora que podemos hacer ante errores de comunicación, como los mencionados anteriormente. En caso de no recibir respuesta del servidor, podemos repetir tantas veces como queramos el paso 3 o el 5, ya que PUT es idempotente. ¿Y si falla el POST del paso 1? Pues repetimos el paso 1. El repetir el paso 1 no es idempotente, por lo que en el servidor se crearán comandos vacíos, pero no ejecutados. Esto no es demasiado grave, ya que el comando no ha llegado a ejecutarse, y no le hemos cobrado dinero a nadie. Por otro lado sólo nos interesa persistir comandos que se han ejecutado, con lo que dichos comandos vacíos no necesitan ocupar espacio en disco.

También podemos tener otro problema con esto del POST duplicado, ¿y si ocurre un ataque, en el que un bot se dedica a ejecutar miles o millones de POSTs? En este caso, tenéis que tener cuidado, ya que si no implementais bien el servidor, puede ocurrir que se os ocupe la memoria RAM y se os colapse. Existe una técnica para crear comandos vacíos que no ocupan espacio en memoria. Esta técnica consiste en crear tickets como identificadores de entidad para los comandos. Un ticket es un identificador, generado por el servidor, que no puede ser falsificado, pero que es fácilmente verificable como válido por éste. De esta forma, en el POST, lo que hacemos es generar un ticket, para identificar un supuesto nuevo comando, pero no creamos en memoria ningún tipo de objeto ni persistimos nada. Cuando nos hacen un PUT para actualizar o ejecutar un comando, cogemos su identificador de instancia, y como es un ticket podemos comprobar si es un identificador legítimo o no. Sólo admitimos la ejecución del PUT si el ticket es válido.

¿Cómo implementamos este sistema de tickets? Para implementarlo necesitamos tres ingredientes: generar un UUID, una clave secreta que sólo conozca el servidor, y una hash criptográfica. El ticket se construye: uuid + «:» + HASH(uuid + CLAVE) Para comprobar si un ticket es válido basta con coger la parte que llega hasta el «:», concatenarla con la clave, hacerle el HASH y compararla con la segunda parte del ticket. Veamos algo de pseudocódigo:

  • Para generar un ticket:
    function generateTicket() {
      var uuid = uuid.newUUID()
      var key = "The server secret key"
      ticket = uuid + ":" + hash_SHA(uuid + ":" + key).toBase64();
      return ticket
    }
    
  • Para validar un ticket:
    function isValidTicket(ticket) {
      var colonIndex=ticket.indexOf(":")
      if(colonIndex==-1)
        return false;
      var uuid = ticket.substring(0, colonIndex);
      var signature= ticket.substring(colonIndex+1);
      if(!uuid||!signature)
        return false;
      var key = "The server secret key"
      var trueSignature=hash_SHA(uuid + ":" + key).toBase64()
      return trueSignature == signature;
    }
    
  • Para actualizar un pedido:
    function updateOrder(RESTRequest req) {
      if(!req.isPut())
         return
      var uri=req.getURI()
      var entityId= uri.substring(0, uri.lastIndexOf("/"))
      if(!entityId||!isValidTicket(entityId))
        return;
      var order=dao.findOrder(entityId)
      if(!order)
        order=order.createOrderWithId(entityId);
      var oldExecute=order.execute;
      order.update(req.getData);
      order.save();
      if(oldExecute&&!order.execute)
        undoOrder(order);
      else if(!oldExecute&&order.execute)
        doOrder(order);
    }
    

Notad que en el POST no se crea ningún objeto, sólo un ticket que se devuelve. El ticket tampoco es almacenado en ningún sitio, ya que podemos verificar su autenticidad sin necesidad de ello. La creación del verdadero objeto, se realiza en el primer PUT recibido, y sólo si se ha hecho PUT sobre una URI que contenga un ticket válido.

Con la capacidad de modelar colecciones, entidades y comandos, ya estais en disposición de diseñar interfaces REST para cualquier problema de negocio. Hasta este punto, podéis publicar la misma funcionalidad que con SOAP, pero a mi modo de ver, de una forma más sencilla y clara. Yo diría que el enfoque REST es más orientado a objetos y el enfoque SOAP es más procedural (publicar un servicio SOAP no se distingue mucho de publicar un servicio COBOL y pasarle una commarea). En próximos posts pienso abordar temas como el autodescubrimiento, por qué no necesitamos nada parecido al WSDL y cómo la distinción entre datos y presentación se vuelve borrosa.

Read Full Post »


Bueno, bueno, recién vuelvo de vacaciones, ¡ y me doy cuenta que no he publicado nada en todo Agosto ! Pues nada, a escribir un poco, que se que a algunos de vosotros os viene bien leer mis ladrillos para combatir el insomnio. Comienza aquí una serie de posts sobre diseño de servicios REST, y como estoy bajo de forma debido a los excesos veraniegos, este primer post sólo pretende abordar lo más básico. En siguientes entregas iré atacando conceptos más complejos. No es mi intención enseñar código, ya que eso depende de la plataforma de desarrollo que uséis, y al fin y al cabo los servicios web se basan en definir una buena interfaz, y no en como estén implementados internamente. Pretendo describir una serie de técnicas de diseño y modelado que os sirvan para definir servicios REST de una manera adecuada a vuestras necesidades. Son técnicas que he ido aprendiendo por mi cuenta mediante la práctica en la vida real, ya que he encontrado bastante poco en la web sobre diseño REST.

Como ya vimos en mi anterior post, el protocolo idóneo para hacer servicios web basados en principios REST es el protocolo HTTP. Veamos como podemos combinar las URIs, con los verbos HTTP y los formatos multimedia para publicar la funcionalidad básicas de un CRUD normalito en forma de servicios REST. Al fin y al cabo nuestro jefe lo primero que va a querer es un «mantenimiento de tablas» para mañana. Sin embargo tened en mente que el REST es más que un CRUD, como iremos viendo a lo largo de la serie. Para enfrentarnos a un CRUD básico tenemos varios patrones de diseño: entidad/colección, singleton y maestro/esclavo.

El más básico es el patrón entidad/colección. Se basa en conceptos clásicos de OO y bases de datos relacionales y los adapta al mundo web. Básicamente consiste en definir entidades de negocio, agrupadas por tipos o clases en colecciones. Más formalmente, una entidad es un recurso REST que representa un objeto del dominio de negocio que es persistente, y tiene una identidad propia. Cada entidad puede tener, opcionalmente, un estado, que contiene información adicional o «datos» sobre la entidad. Estos datos pueden ser leídos y modificados. Las entidades pueden ser borradas y creadas. Ejemplos de entidades: cliente, libro (en una tienda de libros), post (en un sistema de blogs), cuenta corriente, etc.

El hecho de que las entidades necesiten una identidad encaja naturalmente con REST, ya que todos los recursos necesitan un identificador único global, que podemos usar como identidad de la entidad. Obviamente vamos a modelar esa identificación como una URI. Podemos usar cualquier URI que queramos a condición de que sea única por cada instancia de entidad. En el patrón entidad/colección propongo modelar las URIs siguiendo las siguiente nomenclatura:

Donde <sistema> es el nombre de nuestro sistema de información o el nombre de nuestro proyecto, <coleccion> el nombre de la colección a la que pertenece la entidad y por último tenemos el id de la instancia concreta a la que queremos acceder. Este tipo de URIs se llama URIs constructivas. Existe una escuela REST que defiende las URIs opacas sobre las constructivas, en otro post veremos la diferencia y las ventajas e inconvenientes entre ambas. Es importante que el id de instancia no sea predecible, como sería el caso de un numérico autoincremental. Es mejor usar un UUID tipo 4 o un número aleatorio con hash criptográfica. De esta manera nos evitaremos ataques de búsqueda de id de instancias.

Si la entidad, como suele ser el caso, tiene estado, es decir, información públicamente manipulable, el siguiente paso que debemos dar es decidir que tipos mime vamos a soportar. Dichos tipos mime deben poder transportar la información pertinente sobre la entidad. Tal vez podríamos usar application/xml o application/json, para transportar información estructurada. Pero podemos usar también video, imágenes, sonidos, pdf o gráficos vectoriales para transportar información «no estructurada». Como se aprecia un sólo tipo mime puede ser insuficiente para cubrir todos los aspectos funcionales de la entidad. Un detalle importante es que cada operación sobre la misma entidad puede soportar diferentes tipos mime. Incluso en teoría, cada instancia podría soportar un juego diferente de tipos mime en función de su estado, aunque yo nunca me he encontrado con este último caso.

Para poder leer (R) las entidades debemos decidir que verbo HTTP usar. En este caso el verbo adecuado es GET. El verbo GET está definido explícitamente en la especificación HTTP con el significado de leer, así que lo usaremos para esta operación. Además GET esta clasificado como seguro e idempotente. Seguro en este contexto significa que el servidor no va a cambiar su estado, ni a generar ningún efecto secundario (como el envío de un mail) detectable por el cliente, una propiedad interesante ya que al leer no queremos que ocurran cosas «raras». Lo de idempotente significa que podemos repetir cuantas veces queramos la operación, que dado los mismos parámetros, el resultado es exactamente el mismo, asumiendo que el estado del servidor no ha sido modificado mientras tanto, claro. Una lectura típica consistiría en enviar una petición GET a la URI de la entidad. Con la petición debemos enviar la cabecera Accept especificando el tipo mime que soportamos como cliente. El servidor, en caso de éxito, responde con un mensaje con estatus 200, cabecera Content-Type especificando el tipo mime de la respuesta y un cuerpo con los datos. Otra opción es que el servidor responda con 204, indicando que no hay datos que devolver. Los mensajes HTTP en estos casos quedarían:

  • Petición de lectura de una entidad:
    GET https://www.mybooks.com/books/XSR9RFSV43r52 HTTP/1.1
    Accept: application/json,application/xml;q=0.5
    
    
  • Respuesta exitosa, pero sin datos:
    HTTP/1.1 204 No Content
    
    
  • Respuesta exitosa, con datos:
    HTTP/1.1 200 Ok
    Content-Type: application/json
    
    {
      'id':'XSR9RFSV43r52',
      'title':'Como hacerse rico',
      'precio':'20euro'
    }

¿Cómo hacemos las consultas? Sencillo y complejo a la vez. Obviamente podemos hacer una petición GET contra la URI de la colección. En este caso, ¿qué devolvería el servidor? Tenemos varias opciones:

  • Si hay datos, un código 200 y en el cuerpo de la respuesta un array o lista, que contiene enlaces a las entidades pertenecientes a la colección. Esta lista se puede codificar fácilmente en JSON, XML o casi cualquier tipo mime estructurado. Con enlaces me refiero a las URIs de las entidades. Si usamos la nomenclatura de las URIs propuestas anteriormente, basta con mandar el id de entidad, porque concatenándolo con la URI de la colección tenemos la URI de la entidad. El cliente puede después usar estas URIs para acceder a los datos de la entidad. Por ejemplo, los cuerpos de las respuestas HTTP quedarían:
    • Usando application/json:
      ["XSR9RFSV43r52", "X35RFSV4AT52"]
    • Usando application/xml:
      <books>
        <book ref="XSR9RFSV43r52"/>
        <book ref="X35RFSV4AT52"/>
      </books>
  • Con el enfoque anterior tenemos un uso de ancho de banda muy pequeño, es ideal si sólo queremos acceder y operar con los datos de una pocas de las entidades devueltas por la consulta. Si queremos en cambio acceder a casi todas las entidades devueltas, tendremos un problema de rendimiento, al tener que hacer muchos accesos al servidor. Para minimizar este problema podemos traer, no sólo los identificadores de instancia, sino todos los datos de la entidad, de esta manera nos evitamos tener que acceder de nuevo al servidor. Usando application/json, por ejemplo, el cuerpo de la respuesta HTTP contendría:
    [
      {
        'id':'XSR9RFSV43r52',
        'title':'Como hacerse rico',
        'precio':'20euro'
      },
      {
        'id':'X35RFSV4AT52',
        'title':'Los robots oxidados',
        'precio':'18.4euro'
      }
    ]
  • En el caso de que la consulta devuelva muchas entidades los métodos anteriores pueden ser ineficientes. Una opción interesante es estructurar la respuesta en páginas de resultados. De esta forma la consulta no devolvería un array, sino la primera página de resultados. Cada página contendría un array con un número máximo de resultados, y al menos un enlace a la siguiente página. Podemos añadir enlaces adicionales: página anterior, primera página, última página, las 10 siguientes páginas. También podemos añadir datos extra como el número total de entidades devueltas en la consulta. El array de resultados, puede contener como antes, o sólo los enlaces a las entidades, o también los datos de éstas. Si usamos URIs relativas, y application/json, el cuerpo de la respuesta HTTP quedaría:
    {
      'totalBooksFound':54345,
      'pageSize':2,
      'firstPage':'/shop/books',
      'prevPage':'/shop/books',
      'nextPage':'/shop/books?page=2',
      'lastPage':'/shop/books?page=27173',
      'books':[
        {
          'id':'XSR9RFSV43r52',
          'title':'Como hacerse rico',
          'precio':'20euro'
        },
        {
          'id':'X35RFSV4AT52',
          'title':'Los robots oxidados',
          'precio':'18.4euro'
        }
      ]
    }

El usar una opción u otra es cuestión del problema que tengamos a mano, y debéis decidir vosotros. Lo más simple es mandar un array, ya sea en JSON o XML con enlaces a todas las entidades de la aplicación. Lo más complejo, pero más potente, es devolver páginas de resultados. En el caso de que la consulta no devuelva datos podemos devolver o bien un 204 sin cuerpo de mensaje o bien un 200 con un array vacío o una página vacía.

Esto nos resuelve el problema de consultar todas las entidades de una colección. Si queremos restringir la consulta mediante criterios de búsqueda, debemos mandar parámetros al servidor conteniendo dichos criterios, ¿como los mandamos? ¿En cabeceras HTTP? ¿En el cuerpo de la petición? Mi consejo es que uséis una «query string». Dentro de la especificación de las URIs, se define que la parte «query string» de la URI, el medio adecuado para restringir el resultado de una operación HTTP sobre una URI. Así quedaría: http(s)://www.myempresa.com/<sistema>/<coleccion>?<param1>=<value1>&<param2>=<value2> Por ejemplo: https://www.bookstore.com/shop/books?page=4&tituloContiene=robots&precioMaximo=20euro

Para borrar (D) una entidad, el método a usar es DELETE sobre la URI de la entidad. Obviamente este método no es seguro, ya que estamos eliminando información del servidor, pero si es idempotente. Esto nos permite hacer DELETE varias veces sobre la entidad, en caso de que no tengamos claro si la petición anterior se llegó al servidor o no. Las peticiones duplicadas serán ignoradas y no se producirán efectos indeseados en el servidor. La respuesta de un DELETE correctamente ejecutado no transporta datos, y por lo tanto tiene el estatus 204.

Para hacer un borrado masivo, podemos hacer un DELETE  sobre la URI de la colección, en cuyo caso se borrarían todas las entidades pertenecientes a la colección. Desde un punto de vista purista, un DELETE sobre la URI de la colección, sin «query string», debería borrar el recurso colección, con lo que un siguiente acceso a dicho recurso daría un error 404 Not Found, al dejar de existir la colección. Podemos ignorar esta interpretación del significado de DELETE, y sólo borrar las entidades, pero perderíamos interoperabilidad. Otra opción, mucho más interesante, es hacer DELETE sobre la URI de la colección pero con parámetros en su «query string», en cuyo caso sólo se borrarían las entidades que coinciden con el criterio definido en dichos parámetros. Sería un borrado en masa, pero limitado por un critério de búsqueda.

Ahora que podemos leer entidades, consultar colecciones y borrar recursos, ¿cómo podemos modificar el estado de un recurso REST? En HTTP tenemos dos métodos inseguros, es decir, que modifican el estado del servidor. Estos métodos son PUT y POST, ambos permiten «escribir» en el servidor pero son sutilmente distintos. PUT es idempotente, mientras que POST no lo es. De esta forma repetir varias veces un PUT sobre la misma URI con los mismos parámetros y cuerpo de mensaje, tiene el mismo efecto que hacerlo una única vez. Por el contrario repetir un POST contra la misma URI no es lo mismo que hacerlo una única vez.

¿Cómo podemos hacer pues una operación de actualización (U) de los datos de una entidad? Depende. Si queremos sobrescribir el estado de la entidad, o una parte de este, debemos hacer un PUT. Esto es debido a que un UPDATE clásico es idempotente, por lo tanto el método adecuado es PUT. Basta con hacer un PUT a la URI de la entidad, con la cabecera Content-Type indicando el tipo mime del mensaje, y un cuerpo con el nuevo estado de la entidad. Si tiene éxito el servidor devuelve un código 204. Otra opción es que el servidor devuelva un código 200 adjuntando el nuevo estado de la entidad tras la actualización. Esta última opción es buena si el servidor realiza alguna transformación adicional sobre la entidad como consecuencia de la actualización. Una opción de diseño a tener en cuenta es permitir al cliente no incluir toda la información en el mensaje del PUT, sino sólo la información que desea a modificar. Por ejemplo, sólo tendríamos que enviar los campos nombre y edad, si no queremos modificar todos los datos de la persona. Los mensajes HTTP quedarían:

  • Petición de actualización de una entidad:
    PUT https://www.mybooks.com/books/XSR9RFSV43r52 HTTP/1.1
    Content-Type: application/json
    Accept: application/json,application/xml;q=0.5
    
    {
      'title':'Como hacerse rico rápido',
      'precio':'20euro'
    }
    
  • Respuesta exitosa, pero sin datos:
    HTTP/1.1 204 No Content
    
    
  • Respuesta exitosa, con datos:
    HTTP/1.1 200 Ok
    Content-Type: application/json
    
    {
      'id':'XSR9RFSV43r52',
      'title':'Como hacerse rico rápido',
      'precio':'20euro',
      'specialOffer':true <-- Flag specialOffer puesto por el servidor
    }

¿Y una actualización masiva de varias entidades? Hacer un PUT a la URI de la colección con la «query string» adecuada. El servidor sólo debería actualizar las entidades que cumplan los criterios especificados. Puede devolver un 204 o un 200 con información adicional, como cuantas entidades se han modificado.

Finalmente sólo nos queda crear (C) entidades. El protocolo HTTP nos permite crear recursos tanto con PUT como con POST. Yo desaconsejo usar PUT, ya que implica que el cliente decida que URI va a tener el nuevo recurso, lo que añade una nueva responsabilidad sobre el cliente: saber montar la nueva URI de forma correcta, según la nomenclatura definida por el servidor. Esto, además de aumentar el acoplamiento cliente/servidor, puede ser una mála práctica de seguridad. Lo mejor, desde mi punto de vista, es hacer un POST sobre la URI de la colección. Opcionalmente con el POST podemos mandar el estado inicial de la nueva entidad. Si hay éxito, el servidor responde con 201, y opcionalmente con el estado de la nueva entidad en el cuerpo de la respuesta. En la respuesta debe aparecer la cabecera Location indicando la URI de la nueva entidad. Esto permite al servidor controlar las URIs de las entidades. De nuevo podemos diseñar el servidor de forma que todas las entidades tengan un estado por defecto inicial, de esta manera el cliente sólo necesita enviar los valores iniciales de la nueva entidad que difieran del valor por defecto. Los mensajes HTTP quedarían:

  • Petición de creación de una entidad:
    POST https://www.mybooks.com/books HTTP/1.1
    Content-Type: application/json
    Accept: application/json,application/xml;q=0.5
    
    {
      'title':'Como hacerse rico rápido'
    }
    
  • Respuesta exitosa, pero sin datos:
    HTTP/1.1 201 Created
    Location: https://www.mybooks.com/books/XSR9RFSV43r52
    
    
  • Respuesta exitosa, con datos:
    HTTP/1.1 201 Created
    Location: https://www.mybooks.com/books/XSR9RFSV43r52
    Content-Type: application/json
    
    {
      'id':'XSR9RFSV43r52',
      'title':'Como hacerse rico rápido',
      'precio':'20euro' <-- Precio por defecto
    }

Un detalle sobre todo lo anteriormente mencionado, no todas las entidades ni colecciones tienen porqué admitir todos los métodos anteriormente descritos. Podemos tener entidades de sólo lectura o colecciones que no admiten DELETE o POST. Podemos llegar a tener casos de colecciones degeneradas que sólo tienen un miembro y no admiten ni DELETE ni POST. En este caso podemos prescindir totalmente de dicha colección, quedándonos con una entidad que no está asociada a ninguna colección y que no se puede borrar. En estos casos estamos ante el patrón singleton, que en el mundo de REST no es un antipatrón. De hecho nos sirve para modelar cosas como enumeraciones, y más cosas que ya veremos más adelante. La URI constructiva de un singleton se define como:  http(s)://www.myempresa.com/<sistema>/<singleton> Los singletons pueden colgarse de otras colecciones, entidades y singletons. Ej. https://www.bookstore.com/shop/countries/spain/zip-codes

A veces nos encontramos con entidades que tiene un estado enorme, con cientos de campos, o con campos que son arrays con cientos o miles de posiciones. En estos caso transmitir el estado entero de la entidad puede ser ineficaz. En estos casos mi consejo es que refactoriceis vuestras entidades. Al igual que no es bueno tener clases monstruo con muchos métodos y varias responsabilidades, no suele ser una buena señal de diseño tener una entidad con un estado monstruo. Tal vez ese array enorme, es una colección, o esos 30 campos sean en realidad otra entidad.

Finalmente tenemos el patrón maestro/esclavo. En este patrón existen recursos «esclavos» subordinados a otros recursos «maestros». La identidad de un recurso esclavo es dependiente de su recurso maestro, de esta forma para localizar un recurso esclavo, necesitamos la identidad de su maestro. Además si el recurso maestro es borrado, todos sus recursos esclavos son eliminados, con lo que existe una dependencia en existencia. El uso de maestros y esclavos es muy útil para modelar relaciones parte/todo o relaciones de composición. Como recursos REST que son, tienen URI propia, pero desde un punto de vista funcional no poseen una identidad independiente de su dueño, por lo tanto la URI de un esclavo debería ser: http(s)://www.myserver.com/<sistema>/<maestro>/<esclavo>. Ej. Para modelar que cada usuario tiene una lista o carrito de la compra, podemos crear el recurso esclavo shoppingList. En este caso shoppingList es esclavo de cada instancia de la entidad users, con lo que la URI quedaría https://www.mybooks.com/books/users/GOP34/shoppingList

Es interesante notar, que el patrón entidad/colección, es un caso particular del maestro/esclavo, donde la colección es el maestro y cada instancia de entidad esclavo de la colección. Un fallo típico de diseño es el confundir subobjetos del estado de un recurso, con un recurso esclavo, y viceversa. Los subobjetos del estado de un recurso, no poseen ningún tipo de identidad, y no deben tener una URI y por tanto no modelarse como recursos REST. Un ejemplo de subobjeto, que no debe ser modelado como un recurso esclavo, serían las direcciones de un usuario del sistema. Dos direcciones con los mismos datos, son exactamente equivalentes, y por lo tanto no poseen identidad, ¿a que nos parecería raro una lista de direcciones, con direcciones duplicadas?. Sin embargo en un supuesto sistema de banca, donde cada usuario puede tener cuentas, cada cuenta es un recurso en si mismo, aunque sea esclavo del usuario. Ciertamente podríamos tener dos cuentas con el mismo saldo, pero no podrían considerarse equivalentes, de aquí que cada una tenga una identidad diferente y una URI propia.

Como veis el diseñar un CRUD es bastante sencillo si seguimos la filosofía REST y aprovechamos HTTP. El diseño surge de forma tan natural que muchos recién llegados a REST cometen el error de pensar que REST es sólo una manera de hacer CRUD sobre HTTP. No caigáis en esta tentación, ya que va a limitar vuestros diseños de servicios REST y forzaros a pensar en «tablas» en vez de en una web de objetos accesibles por internet.

Como prometí este era un post sencillito, que no estoy para muchos trotes ¡ En el próximo post más !

ACTUALIZACION: Mi amigo Thorsten me ha señalado un par de erratas que he corregido.

Read Full Post »


Continuando con el tema de los servicios web, en este post voy a hacer una introducción a REST. Os prometo que este será breve (para mis estándares) y en próximos post nos metemos más en detalle. Lo primero es aclarar que REST no es una tecnología, ni siquiera una arquitectura, sino una familia de arquitecturas. Cualquier arquitectura de servicios distribuidos que cumpla con una serie de requisitos se puede considerar como una arquitectura REST. Estos requisitos o propiedades son los siguientes:

  • No se publican servicios RPC. En arquitecturas REST, los servicios no publican un conjunto arbitrario de métodos u operaciones. Por ejemplo, en REST no podemos publicar una interfaz «IGestionEmpleados» con métodos «addEmpleado», «removeEmpleado» o «buscarEmpleadosEnEdadDeJubilacion».
  • En REST lo que se publica son recursos. Un recurso se puede considerar como una entidad que representa un concepto de negocio que puede ser accedido públicamente. Un ejemplo de recurso sería simplemente «EmpleadosDeLaEmpresa» y otro podría ser «Empleado número 33»
  • Cada recurso, como buena entidad que se precie, y de acuerdo a los principios de OO, posee un identificador único y global, que lo distingue de cualquier otro recurso, aunque ambos tuvieran exactamente los mismos datos. En el caso de «Empleado 33», este sería diferente de «Empleado 40», aunque tuvieran el mismo nombre, sueldo, etc.
  • Cada recurso posee un estado interno, que no puede ser accedido directamente desde el exterior. Lo que sí es accesible desde el exterior es una o varias representaciones de dicho estado. Por representación se entiende un formato de datos concreto usado para la transferencia de una copia del estado público del recurso entre el cliente y el servidor. La implementación del recurso decide que información es visible o no desde el exterior, y que representaciones de dicho estado se soportan. Una representación de «Empleado 33» podría ser un documento XML con la información accesible de este. Otra representación sería un documento HTML y otra podría ser un JSON. No sólo podemos representar el recurso como datos estructurados, hay que echarle imaginación. Podríamos pedir por ejemplo, una representación en formato imagen PNG del recurso, tal vez esto devolvería una foto del empleado, o un gráfico de su productividad o su huella dactilar.
  • Si no podemos definir operaciones arbitrarias sobre el recurso, ¿cómo podemos operar con él? En REST todos los recursos comparten una interfaz única y constante. Todos los recursos tienen las mismas operaciones. Las operaciones nos permiten manipular el estado público del recurso. En un sistema REST típico se definen cuatro operaciones.
  • CREATE. En esta operación el cliente manda al servidor una petición para crear un nuevo recurso. Opcionalmente el cliente puede mandar una representación del estado inicial de este recurso. El servidor responde con el identificador global del nuevo recurso.
  • DELETE. En esta operación el cliente elimina un recurso del servidor. El cliente necesita saber el identificador del recurso.
  • READ. Con esta operación el cliente puede leer una representación del estado de un recurso, identificado con su identificador global. El cliente puede especificar que tipos de representaciones entiende. Aquí lo que ocurre realmente es que se copia el estado del recurso en el servidor y se pega en el cliente. Ambas copias del estado no se mantiene sincronizadas. El servidor puede cambiar el estado real del recurso y el cliente, de forma independiente, puede modificar su copia local del estado del recurso.
  • UPDATE. Como el servidor y el cliente tienen una copia diferente del estado, el cliente puede usar esta operación para sobrescribir o grabar su copia del estado en el servidor. De esta manera se puede actualizar el estado del recurso con las modificaciones hechas en el cliente.
  • La implementación del servicio es libre de prohibir alguno de estos métodos para un recurso en concreto. También debe definir el modelo de datos que se va a publicar y que representaciones soporta. Lo que no puede hacer es inventarse operaciones de forma arbitraria. Las operaciones son siempre las mismas.
  • Los distintos recursos se pueden interrelacionar y referenciar entre si mediante sus identificadores globales.

Una confusión típica es pensar que REST no es más que un CRUD llevado a la web, ¿no será mi BBDD relacional un sistema REST? No exactamente. Es parecido, pero existen algunas diferencias sutiles e importantes entre un CRUD simple o una BBDD con una arquitectura REST:

  • Al contrario que en un CRUD típico, como una BBDD, el servidor puede soportar muchas representaciones de un mismo recurso: xml, pdf, png, gif, excel, html, json, texto, comaereas, churros binarios, etc.. Hay pocas BBDD que hagan esto.
  • No penséis que las operaciones REST se limitan a hacer CRUD tradicional, no se trata solo de persistir nuestros cambios. Cuando hacemos un UPDATE, nuestra implementación del recurso además de grabar el estado en soporte persistente, debe hacer otras cosas, como validaciones de negocio o actualizaciones en otros recursos para mantener la consistencia global del sistema. Esto sí que se parece a una BBDD con triggers e integridad referencial, pero no es CRUD en el sentido de que no nos limitamos a grabar y ya está. Una DAO es CRUD, un recurso REST no (excepto en los casos más simples).
  • Los recursos mantiene interrelaciones entre si. La topología de estas interrelaciones es compleja, puede ser un grafo arbitrario, con ciclos, o un simple árbol. Es más, los recursos de nuestro sistema se pueden interrelacionar con recursos en sistemas de terceras partes, ya que el identificador es único y global.

Algunos estareis pensando: «¿Existe en la actualidad alguna arquitectura REST? Estas cosas tan modernas tardan un tiempo en madurar, y yo no quiero usar una tecnología que esté verde». Pues resulta que REST es un enfoque maduro con un claro ejemplo existente en la actualidad: la world wide web, o web a secas. Veamos si la web tiene propiedades REST:

  • La web está compuesta de recursos, cada página web puede considerarse un recurso.
  • Cada recurso tiene un identificador único global, que es su URI (o URL para los antiguos o IRI para los más modernos). Usando una URL podemos llegar a cualquier recurso en la web.
  • Dada una URI, y mediante el protocolo HTTP, podemos operar sobre estos recursos. La operación a realizar se especifica mediante el verbo HTTP. Mediante cabeceras especiales como Accept o Content-Type se puede especificar que representaciones entienden el servidor y el cliente y que representación se usa en un mensaje concreto para transporta el estado del recurso.
  • El verbo GET hace la operación READ.
  • El verbo DELETE hace la operación DELETE.
  • El verbo PUT se usa normalmente para hacer UPDATE
  • El verbo POST se usa normalmente para hacer CREATE.
  • Las representaciones a usar se especifican mediante los llamados tipos mime. La mayoría de los tipos mime son estándares, como xml o json. El usar tipos mime estándar facilita la interoperabilidad.

La aplicación cliente típica de la web es el navegador. Los navegadores se dedican a hacer GETs sobre URIs para obtener representaciones de las distintas páginas web. En el caso habitual cada página sólo admite una representación, típicamente HTML o PDF o texto plano o imágenes. Pero eso no significa que una misma URI no pudiera soportar distintas representaciones.

Lo interesante de todo esto es que la web es un ejemplo perfecto de servicios distribuidos a nivel global totalmente interoperable (o casi). La web, y el protocolo HTTP es una arquitectura REST. La web son servicios REST que en general sólo soportan HTML. Casi cualquier página HTML es interoperable con casi cualquier navegador, y casi cualquier página HTML puede interoperar (referenciar) con otra página HTML construida por un tercero. Es sorprendente que con este claro ejemplo, presente en nuestro día a día, llegada la hora de pensar en como hacer servicios web, no se viera lo que tenemos delante de la cara, sino que se inventara algo como SOAP y WSDL. Mmmm, sospechoso.

Como veis podemos usar HTTP, URIs y tipos mime, para publicar servicios de datos, no meras páginas, que soporten multitud de representaciones y sean conformes a los principios REST. A este tipo de servicios de datos es a los que comúnmente se llaman servicios REST. Sin embargo aun tengo que contar por qué desde mi punto de vista los servicios REST son mejores que SOAP. Las razones son varias:

  • HTTP es un protocolo que sigue los principios REST, por lo tanto hacer servicios REST es algo que aprovecha toda la infraestructura de la web ya existente: cache, proxies, firewall, compresión, etc. No se trata de inventar un protocolo y ver como meterlo con calzador para que encaje dentro de HTTP, sino usar el propio HTTP.
  • La posibilidad de usar múltiples representaciones o formatos de datos, de forma natural, es una ventaja indiscutible. No hay que extender ningún protocolo, o limitarse a XML, el protocolo HTTP ya lo soporta de forma natural. Si usamos un poco de imaginación esto puede ser una característica muy potente.
  • Los servicios REST tienen menor acoplamiento que los servicios basados en SOAP. Esto lo veremos en futuros posts.
  • No necesitamos herramientas complejas, ni montones de código generado e inmantenible. Un simple framework nos basta. Para sistemas sencillos no necesitamos ni eso.
  • Una aplicación AJAX o un móvil tienen la capacidad computacional suficiente para actuar como cliente de servicios REST.
  • Es autodescubrible, no se necesita algo como un WSDL. Esto lo veremos también en futuros posts.

En el próximo post pienso aplicar de forma más concreta todos estos principios al mundo de las aplicaciones empresariales, y veremos como de sencillo es publicar y consumir un servicio REST. Así que ya sabéis, si queréis hacer servicios web usad los mismos principios que hicieron la web posible, usad REST.

Read Full Post »


Hola a todos, lamento no haber escrito un post la semana pasada, pero la celebración de la copa del mundo de fútbol, junto con algunos imprevistos, lo impidió. Con este post pienso comenzar una serie que trate sobre como hacer servicios web usando el enfoque REST, de paso descansamos un poco de todo el tema del Agile y el TDD, aunque no os preocupeis ya volveré a dar la brasa sobre esos temas.

En este primer post de la serie pretendo hacer una introducción a los servicios web y explicar por qué la tecnología SOAP y WSDL (junto con toda la pila WS-*) no es una buena aproximación a la implementación de servicios web. Para ello tendremos que aclarar primero una cuestión fundamental, ¿qué entendemos por servicio web? Un servicio web es una pieza de software que posee las siguientes propiedades:

  • Proporciona una funcionalidad de negocio.
  • Esta funcionalidad es accesible en remoto a través de la web. Ojo, accesible a través de la web, no a través de un protocolo especial, ni de una infraestructura especial, ni en una topología de red especial. Fijaos que esta condición excluye tecnologías como por ejemplo CORBA, ya que ésta última no usa protocolos web. Esta condición garantiza que la invocación de un servicio web se pueda realizar aprovechando la infraestructura de la web ya existente, sin necesidad de instalar nada especial. La tecnología elegida para publicar servicios web debería aprovechar lo mejor posible la infraestructura web ya existente, para conseguir una mejor interoperabilidad y calidad de servicio.
  • Proporciona una interface bien definida, ocultando la implementación real. El servicio puede estar implementado en cualquier tecnología, por ejemplo COBOL. La tecnología real de implementación, y los detalles de ésta, no son importantes y no deben ser visibles al consumidor del servicio.
  • Interoperable, el proveedor del servicio y el consumidor pueden estar en tecnologías distintas, y aun así poder interactuar. Como comenté anteriormente no debería ser necesario montar una infraestructura especial para que un cliente pueda invocar a un servicio.

Desde el punto de vista del nivel de interoperabilidad de los servicios web, yo distingo tres categorías:

  • Privados. El servicio web sólo va a ser consumido por clientes desarrollados por la misma organización que lo creó.
  • Públicos. El servicio web, puede además ser consumido por clientes de otras organizaciones, con los que previamente se ha negociado el modo de acceso. Como caso típico tenemos los escenarios B2B. Un ejemplo es el tarificador de seguros que es llamado por un portal de búsqueda para comparar precios para tu seguro del automóvil.
  • Globales. El servicio web puede ser consumido por cualquier cliente en el mundo. No es factible realizar una negociación sobre el modo de acceso al servicio. Normalmente en este caso se crea una página web documentando la API del servicio y qué protocolo se va a usar. Algunas organizaciones como eBay, Amazon, Google, Yahoo, Facebook o Twitter necesitan este nivel de interoperabilidad.

Es claro que cuanto mayor nivel de interoperabilidad queramos alcanzar, necesitamos un menor nivel de acoplamiento entre el consumidor y el proveedor del servicio. El acoplamiento será mayor cuanta más información necesite el cliente para poder invocar al servicio. Cuanto mayor acoplamiento, mayor cantidad de documentación tendrá qué leer el desarrollador del cliente, y más esfuerzo se necesitará para la programación de éste. El hecho de que los servicios web oculten los detalles de su implementación a través de una interface es bueno en este sentido, ya que el cliente no necesita saber detalles sobre cómo implementa el servicio su funcionalidad para invocarlo. Otra buena práctica de desacoplamiento es que el servicio sea stateless, de esta manera el cliente no necesita almacenar el estado de la conversación para poder invocar al servicio correctamente, sólo necesita comprender los parámetros que va a enviar.

Armados con estos conocimientos básicos estamos en condiciones de comparar distintas tecnologías para construir servicios web. Nos fijaremos en la capacidad de la tecnología para usar la infraestructura web, si es capaz de ofrecer una interfaz que oculte la implementación, si permite el uso de distintas tecnologías en el lado cliente y servidor, y el grado de interoperabilidad que puede proporcionar, es decir, el nivel de desacoplamiento que ofrece.

Analicemos la tecnología más popular para hacer servicios web, la pila WS-*. La pila WS-* es un conjunto de protocolos y estándares para realizar servicios web basados en XML. La verdad es que son muchos protocolos y estándares, y bastante complejos, tantos y tan complejos que yo no conozco dos implementaciones completas de la pila WS-* en funcionamiento e interoperables entre si a todos los niveles. Sin embargo, dentro de todos estos estándares, nos podemos fijar en los dos más usados (y casi los únicos): SOAP y WSDL.

SOAP nos permite invocar procedimientos remotos usando XML. Actualmente SOAP se ha extendido para que soporte adjuntos binarios. La verdad es que si ya de por si, el XML es un formato de datos muy poco conciso, el formato SOAP es bastante verbose (al igual que mis posts). Esto evidentemente es un problema cuando queremos trabajar con anchos de banda reducidos (modems, móviles 3G, etc.). Otra característica importance de SOAP es que es neutral con respecto a la infraestructura. Esto te permite invocar procedimientos remotos a través de distintos protocolos, como por ejemplo SMTP, MQSeries, y como no, HTTP. Fijaos que SOAP no fue diseñado para aprovechar HTTP, sino que permite usar HTTP entre otros protocolos. ¡Qué chulo! debieron de pensar los arquitectos de SOAP. Bueno, lamento comunicar que a mi lo que me interesan son los servicios web, lo que me interesa es invocar a un servicio de la forma más eficiente usando la web, el hecho de que SOAP pueda invocar servicios no web me parece irrelevante, no me interesa. El hecho de que SOAP sea neutral con respecto a la infraestructura, evita que éste pueda aprovechar cualquier ventaja de la infraestructura web, e incluso en algunas ocasiones puede provocar problemas.

Por otro lado, tenemos WSDL, que es un formato XML que te permite describir, de forma declarativa, el modo de acceso al servicio. El WSDL es el documento o contrato que debe distribuirse a todos los clientes para que éstos tengan toda la información necesaria que les permita acceder al servicio. También es usado por los frameworks de publicación de servicios web y por las herramientas de generación de código, tanto a nivel cliente como servidor. En un documento WSDL hay que definir varios artefactos: tipos de datos, mensajes, operaciones, portTypes, bindings, ports y services. Casi nada. Los tipos de datos se definen con XML Schema, recordemos que SOAP sólo usa XML. Los mensajes definen los documentos XML que van a moverse entre el cliente y el servidor. Para definir un mensaje hay que especificar de que partes o secciones se conforma este y de que tipo de datos son cada una de estas secciones. Las operaciones definen parámetros de entrada y salida y posibles excepciones. Cada parámetro y excepción debe corresponderse con un mensaje. Por fin llegamos al portType, que es simplemente la interfaz del servicio, es decir, el conjunto de operaciones que soporta el servicio. Hasta aquí la parte «abstracta» o lógica del WSDL. Daos cuenta de todo lo que hemos tenido que definir para especificar una interface. Si os fijais el enfoque real de todo esto es RPC, aunque existan cosas como «SOAP Document binding». Ahora viene la parte «concreta» del WSDL. Como SOAP es neutral al protocolo tenemos que definir que protocolos se va a usar y como se va a usar. Para ello tenemos los bindings, que define el protocolo a usar (en este caso siempre SOAP) y como se va a codificar los mensajes usando SOAP (RPC o Document), para cada mensaje y operación. Después está el port, que define la dirección donde se va a publicar cada binding, dirección que depende del protocolo de transporte usado. En caso de usar HTTP, será una URI. Finalmente el servicio es el contenedor de todos los ports disponibles. Imaginaos escribir todo esto para un sistema de verdad, imaginaos tener que leer todo esto para consumir un servicio. Como veis en un WSDL hay mucha información y muy compleja, lo que genera mucho acoplamiento entre el consumidor y el proveedor del servicio.

Bien el resultado de las primeras versiones de esto del SOAP y del WSDL a nivel de interoperabilidad, fueron… como decirlo… una buena pifia. Basicamente la interoperabilidad era muy limitada. La complejidad del estándar llevó a implementaciones de distintos fabricantes no interoperables. Debido a esta situación se sacó otro estándar el WS-I Basic Profile, encargado de aclarar y simplificar SOAP y WSDL para una mayor interoperabilidad. En el fondo se recomienda usar una versión restringida de SOAP y aclara aspectos del WSDL. Con el tiempo, y este nuevo estándar, se ha llegado a conseguir una interoperabilidad aceptable, desde mi punto de vista se puede conseguir interoperabilidad a nivel público, pero a nivel global es más discutible.

El nivel de complejidad de estos estándares hace que un programador normal no sea capaz de publicar un servicio web o de consumirlo. La única manera práctica de trabajar con esta tecnología es usar herramientas potentes de generación de código en conjunción con un framework potente. Este enfoque tiene varios problemas a mi manera de ver las cosas:

  • Normalmente estas herramientas suelen costar dinero. Afortunadamente esta circunstancia está desapareciendo.
  • El código generado por las herramientas es siempre inmantenible.
  • La configuración generada por las herramientas suele ser inmantenible.
  • El WSDL hay que escribirlo. Aunque lo hagas con un editor visual, tienes que seguir entendiendo como combinar portTypes, messages, ports, etc. Este WSDL hay que mantenerlo después.
  • En el caso de que puedas generar el WSDL a partir de la interfaz que hayas programado en tu lenguaje favorito, tienes que cerciorarte que sigue WS-I Basic Profile, para que sea interoperable.

Desde el punto de vista del mantenimiento surge el problema de que nadie entiende nada de lo que hace el sistema, es muy difícil de razonar lo que está ocurriendo. Es curioso, hace unos meses ayudé a un compañero a resolver un bug en un web service basado en SOAP. Aparte de que fue infernal, lo gracioso es que para poder averiguar donde estaba el fallo, tuvimos que rastrear los mensajes HTTP, para averiguar que una cabecera estaba siendo enviada mal, y pudimos cambiar el binding del WSDL para arreglarlo. Es irónico, se supone que SOAP te esconde del HTTP, pero tuvimos que aplicar nuestros conocimientos de HTTP para poder averiguar donde estaba el error. No quiero saber lo que hubiera pasado con alguien que no supiera HTTP.

Otro problema de SOAP es que consume bastantes recursos de computación, con lo que es difícil que pueda ser usado por clientes como móviles o aplicaciones RIA (recordemos que el JavaScript no es muy rápido que digamos en los browsers actuales). Esto es un fallo grave, ya que lo ideal es que una aplicación móvil o RIA pudiera acceder a servicios web.

En resumen, los problemas de SOAP/WSDL son:

  • Es complejo y difícil de entender.
  • Necesitas un framework complejo.
  • Necesitas una herramienta compleja para que te genere el código, código generado que es inmantenible.
  • No funciona muy bien con dispositivos móviles y aplicaciones RIA basadas en javascript.
  • Es ineficiente desde el punto de vista del ancho de banda.
  • Sólo sirve para XML (con adjuntos binarios)
  • No aprovecha la infraestructura web, como caches, negociación de formatos, negociación de idioma, seguridad, sistema de concurrencia optimista, etc. Por supuesto no es firewall friendly. Aunque lo hayan vendido como tal, no lo es. SOAP usa el protocolo HTTP como un mero medio de transporte. No aprovecha sus características, y subvierte la semántica de este último. Esto hace que las llamadas SOAP sean «opacas» desde el punto de vista de la infraestructura web, como caches o firewalls. De hecho necesitas un nuevo tipo de firewall, el firewall de aplicación, que entienda SOAP y que necesita parsear todo el cuerpo de la petición para poder aplicar reglas de seguridad. Adios a securizar por URLs, puertos y verbo HTTP.
  • El programador necesita ser un «master del universo» y saber rezar.
  • Rellene aquí su experiencia de terror favorita con SOAP:__________________________________

Debido a todas estas complicaciones, algunos movimientos como SOA y BPM no han tenido una implantación real (aunque sí fingida) en las empresas. Si ya hay problemas para usar SOAP para comunicación punto a punto, imaginaos la que se monta con todo esto del SOA, ESBs y BPM. Es una pena, porque los conceptos de SOA y BPM son realmente valiosos, el fallo ha sido basarlos en una tecnología que no es la adecuada: SOAP.

Consciente de todos estos problemas, algunos arquitectos con sentido común, y algunas empresas que necesitan interoperabilidad global empezaron a impulsar un nuevo (en realidad viejo) enfoque para la construcción de servicios web. Este enfoque es REST. En el próximo post voy a explicar de que va esto de REST y qué es lo que tiene de bueno. Como veremos en el siguiente post la principal ventaja de REST es que… «it just works», ah no, que eso eran los apple.

Los que piensen que esto del REST es una patraña, debeis tened en cuenta que:

  • Está en uso por las empresas más grandes del mundo a nivel global, y a ellos les funciona.
  • Algunas grandes empresas, que venden SOAP, ahora están empezando a vender REST
  • La última versión de WSDL, WSDL2.0, ahora soporta no sólo SOAP sino también REST. Además WSDL2.0 ha sido simplificado, eliminando por ejemplo, el concepto de mensajes, y ha sido modificado para que sea más REST friendly.

Curioso este movimiento hacia WSDL2.0 ¿Debemos usar WSDL2.0 para nuestros servicios REST? No gracias, no necesitamos WSDL. Como veremos los servicios REST son autodescubribles y basado en estándares, y no necesitamos ningún WSDL, ni similar, para poder trabajar con REST. Desde mi punto de vista WSDL2.0 es una acción desesperada para intentar salvar SOAP/WSDL y la pila WS-*.  Bueno, hasta el próximo post, donde nos meteremos en faena con esto del REST.

ACTUALIZACION: Hoy en el trabajo un compañero me ha contado una historia de terror. Un WSDL de un proyecto al pasarse por la herramienta de generación de código JAVA, ha generado más de treinta mil lineas de código. En fin, para los que penseis que soy exagerado.

Read Full Post »