Feeds:
Entradas
Comentarios

Archive for septiembre 2010


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 »


Después del tocho sobre REST de la semana pasada, ahora toca cambiar de tercio. Seguramente algunos de vosotros habréis oído hablar de los DSL o Domain Specific Languages. En particular este es un tema en el que vengo trabajando desde hace años, y que me ha resultado bastante interesante y productivo.

¿Qué son los DSL? Simplemente podemos considerar como DSL cualquier lenguaje que esté especializado en modelar o resolver un conjunto específico de problemas. Este conjunto específico de problemas es el llamado dominio de aplicación o de negocio. La mayor parte de los lenguajes de programación no se pueden considerar DSL ya que no están diseñados para resolver un conjunto específico de problemas, sino para resolver cualquier tipo de problema. Son pues, lenguajes generalistas (o turing completos).

El usar lenguajes generalistas ha tenido bastante aceptación y éxito hasta el día de hoy, de hecho poseen una ventaja indudable: con dichos lenguajes podemos resolver cualquier tipo de problema. Sin embargo esta ventaja no es gratis, al contrario, hay que pagar un precio, que es que no todos los problemas son igualmente sencillos de resolver. De hecho, dependiendo del lenguaje generalista que usemos, un mismo problema es más sencillo o más difícil de resolver. Es gracioso que se hayan producido muchas controversias por cual es el mejor lenguaje, cuando en realidad unos lenguajes son buenos para una cosa y malos para otra, y que los principales defensores de un lenguaje sólo tienen en cuenta las tareas en las que su lenguaje favorito es bueno. Normalmente suelen ser las mismas tareas que ellos se encuentran en su día a día laboral. Por ejemplo, trabajar con listas o hacer algoritmos recursivos es muy simple en LISP, sin embargo en BASIC o COBOL no es tan sencillo. Supongo que en cambio, con COBOL es sencillo definir algoritmos de negocio. Otro ejemplo, generar una página web dinámica es relativamente sencillo en PHP pero más complejo en JAVA. ¡ Eh ! Espera, en JAVA hacer una página web está tirado, pones una JSP y entonces… No hombre, el lenguaje JSP no es lenguaje JAVA, es un DSL…

Si cada lenguaje puede resolver cualquier problema, pero no todos con la misma sencillez, es lógico pensar que para cada problema que tengamos, podemos buscar el lenguaje que mejor se adapte a él. Al fin y al cabo, hay que usar la herramienta adecuada para cada caso. En su momento algunas mentes preeminentes se dieron cuenta de que el problema consistía precisamente en que los lenguajes generalistas eran demasiado ambiciosos al tratar de poder solventar cualquier problema. De hecho sería más adecuado poder contar con lenguajes especializados en solucionar los problemas que tuviéramos a mano. Es la misma diferencia entre tener un cuchillo multiuso para cualquier cosa, o un set de cuchillo de postre, de carne, de pescado y un bisturí y elegir los cuchillos que mejor nos vengan. Si sé que ese día voy a practicar una operación a corazón abierto, y después comeré pescado, mejor me llevo el bisturí y la paleta de pescado (para ser cirujano hay que tener el estomago muy fuerte).

De esta forma nació el enfoque de los DSL, en vez de usar un lenguaje generalista, puedo usar diversos lenguajes de propósito específico, para resolver los diferentes problemas que surgen al implementar una aplicación. Por lo tanto sería muy eficiente contar con varios DSL, uno para cada tipo de problema con el que nos solemos encontrar, y para los problemas no tan típicos (esperamos que el 20% o menos) solucionarlos con lenguajes generalistas. Algunos pensareis que esto de anticipar los problemas que voy a tener es un poco irreal, pues ni mucho menos. Desde el punto de vista puramente técnico, existen problemas que voy a tener que resolver casi siempre:

  • Definición de interfaces de usuario.
  • Seguridad. Aquí podemos distinguir dos subproblemas separados: autenticación y autorización.
  • Gestión transaccional.
  • Persistencia.
  • Queries.
  • Validación.
  • Definición de tests automatizados (no me puedo resistir a meter TDD con calzador, je, je)
  • Análisis léxico y sintáctico de lenguajes (parser) para construir compiladores

Obviamente estos son sólo algunos de los problemas técnicos que nos solemos encontrar día a día, hay muchos más claro ¿No sería interesante contar con un DSL específico para resolver estos problemas? De este modo podríamos especificar en cada uno de los DSLs los requisitos de nuestro sistema de forma específica para cada tipo de problema.

Aunque parezca mentira, ya existen algunos DSL que conviven con nosotros, pero tal vez no hemos pensado en ellos como tales. Algunos ejemplos:

  • SQL para definir queries.
  • XML para transportar datos, y a veces nos viene bien para persistir.
  • HTML para definir interfaces de usuario. Curiosamente este DSL se ha ido «extendiendo» para ir convirtiéndolo cada vez más en un lenguaje generalista. No es de extrañar que sea difícil definir aplicaciones usando enteramente HTML.
  • CSS para definir interfaces de usuario a nivel de presentación pura.
  • JSP. Para definir (otra vez) interfaces de usuario web.
  • WSDL, para especificar interfaces de datos de servicios web.
  • XML de configuración de spring, para definir grafos de objetos.
  • XML de hibernate para hacer mapeo entidad/relación.
  • jMock para definir Mock Objects.
  • ANTLR para definir gramáticas LL(*)
  • Expresiones regulares (no se puede vivir sin ellas)

Como vemos, ya estamos conviviendo con DSLs, aunque no nos hayamos dado cuenta. De hecho existe un ciclo de vida en la evolución de una solución a un problema dentro del software. Primero resolvemos el problema como se nos ocurra. Después, conforme ganamos experiencia, al resolver el problema varias veces en contextos distintos, encontramos buenas prácticas o «recetas», que nos permiten resolver el problema mediante copy/paste. En una etapa posterior de maduración, conseguimos abstraer esas recetas, en una librería que sigue un determinado diseño, y obtenemos un patrón de diseño al abstraer el diseño de la implementación en si. Una vez que tenemos varios patrones de diseño que resuelven distintos problemas de la misma categoría, podemos implementarlos juntos, y obtenemos un framework. Finalmente, y este es el paso menos conocido, el framework se hace cada vez más configurable y automatizado, con lo que éste termina evolucionando en un DSL. Como ejemplo tenemos el caso de la inyección de dependencias: todo surgió por la necesidad de instanciar objetos, se vio que era interesante aprovechar el polimorfismo y usar interfaces, después se decidió que no era bueno usar «new» porque entonces no podíamos cambiar la implementación de la interfaz alegremente, por lo que surgió el patrón «factory method», que después evolucionó en el patrón «factory», al evolucionarlo a una «factory» configurable nació la inyección de dependencias y de ahí Spring y su XML, que es un DSL para configurar «factories».

No quiero que os quedeis con la idea de que los DSLs son cosas que resuelven problemas «técnicos». Hasta el momento he mostrado lo que son los DSLs «técnicos», pero es que también existen DSLs de negocio. Existen muchos dominios de negocio, podemos tener aplicaciones de banca, de inversión en bolsa, tiendas electrónicas, seguros, etc. Por supuesto cada área se puede subdividir en dominios más pequeños. Por lo tanto parece una buena idea el poder definir DSLs de negocio, que nos permitan especificar, de forma sencilla y eficaz, los distintos conceptos, reglas y validaciones, de un dominio de negocio concreto.

Así pues, desde el punto de vista del tipo de problemas que resuelven, tenemos dos tipos de DSL, DSLs de negocio y DSLs técnicos. Sin embargo existe otra forma diferente de categorizar los DSLs, según el enfoque a la hora de implementarlos. De esta manera tenemos dos tipos:

  • DSLs externos. Un DSL externo es un lenguaje en si mismo, independiente de cualquier otro lenguaje. Posee su propia sintaxis, compilador y/o interprete, e incluso puede llegar a tener su propio IDE. Ejemplos de DSLs externos son SQL o ANTLR. Los DSLs externos son el típico caso en el que uno piensa cuando se imagina un DSL, un lenguaje nuevo y aparte de los que ya existen.
  • DSLs internos. En este enfoque no construimos un lenguaje desde cero, sino que aprovechamos un lenguaje ya existente para construir nuestro DSL. Usando un poco de imaginación y exprimiendo los trucos del lenguaje anfitrión y su sintaxis, podemos construir una librería o framework que implemente el DSL de forma nativa al lenguaje anfitrión. Algunas técnicas usadas son los patrones «method chaining» y «builder», el uso de métodos factoría estáticos, import estáticos, tipos y métodos paramétricos (genéricos) y por supuesto mucha reflexión, metaprogramación y los siempre agradecidos proxies dinámicos. Un excelente ejemplo de DSL interno es el propio jMock. Los DSL internos también son llamados «fluent interface» o «fluent API». Yo, pobre de mi, no sabía que existía esta denominación, por lo que se me ocurrió llamar «API Sexy» a un DSL que había construido, con el consiguiente cachondeo de mis compañeros.

Si los DSLs son tan maravillosos, ¿por qué no estamos todos usándolos de forma más intensiva? El problema principal es el coste de desarrollar un buen DSL. Como norma general, el enfoque tradicional hacia los DSL ha sido el de DSL externos. Construir un DSL externo es bastante costoso, en realidad es construir un lenguaje de programación nuevo, con su compilador o interprete. Necesitamos saber análisis léxico, sintáctico, transformación de ASTs, generación de código, etc. Vamos todo lo que nos enseñaron en la asignatura de compiladores. Normalmente para esto contamos con herramientas como ANTLR, javaCC, Lex, Yacc, etc. Evidentemente todo esto es complejo y lamentablemente no está al alcance del desarrollador medio de aplicaciones web de empresa (sic). Otro punto problemático es el IDE, si yo me construyo un DSL externo, el IDE no me va a dar soporte, ni a nivel de errores de compilación, ni a nivel de refactorización, ni tan siquiera ayuda contextual (no sabemos vivir sin el CTRL+SPACE). Si quiero todo esto me tengo que construir un IDE nuevo o extender uno existente, tarea de nuevo costosa. Obviamente la ventaja de un DSL externo frente a uno interno es que el DSL tendrá la sintaxis y gramática que queramos, mientras que el DSL interno estará restringido por el lenguaje anfitrión.

Sin embargo el enfoque DSL ha tenido un resurgimiento al introducirse el concepto de DSL interno. Un DSL interno es mucho más sencillo de construir, no difiere mucho de construir un framework, tal vez se necesita una dosis extra de uso de reflexión y metaprogramación, pero al fin y al cabo no tenemos que andar con gramáticas y demás. Por otro lado, al no ser en realidad más que un API del lenguaje anfitrión, el IDE nos va a proporcionar ayuda. Ciertamente no todos los lenguajes son lo suficientemente flexibles como para permitir construir un DSL interno. Es casi imposible hacerlo en COBOL o en C (que yo sepa). En JAVA la dificultad es media. Pero en algunos lenguajes como Groovy, ¡resulta sencillo! En general cuanto más flexible sea el sistema de tipos del lenguaje y su sintaxis, y cuanta más capacidad de metaprogramación tenga, más sencillo construir un DSL interno. Lenguajes como Groovy o Ruby son ideales para esto, mientras que lenguajes con tipado fuerte suelen ser más problemáticos.

Sin entrar en como construir un DSL interno, veamos algunos ejemplos en Groovy:

  • Basándonos en el concepto de builders de groovy podemos definir un XML dinámico:
    import groovy.xml.MarkupBuilder
    StringWriter writer = new StringWriter()
    def build = new MarkupBuilder(writer)
    build.books
    {
      book(id:'Juan Palomo', price:23.44.euro)
      {
        author(ref:'author23')
        author(ref:'author3')
        synopsis 'En un bar lejano...'
      }
      book(id:'Juan Palomo 2', price:34.euro)
      {
        author(ref:'author3')
        synopsis 'De vuelta a casa...'
      }
      book(id:'La vida loca', price:3.44.euro)
      {
        author(ref:'author12')
        synopsis 'En un bar cercano...'
      }
    }
    println writer.toString()
  • O quizás, usando un hipotético servicio REST:
    import groovy.xml.MarkupBuilder
    def dataSource=new myrest.RESTDatasource('https://www.bookstore.com/shop/books?bestSeller=true');
    StringWriter writer = new StringWriter()
    def build = new MarkupBuilder(writer)
    build.books
    {
      dataSource.each
      {
        book(id:it.title, price:it.price.euro)
        {
          authors.each { author(ref:it.id) }
          synopsis it.synopsis
        }
      }
    }
    println writer.toString()

No esta mal, ¿eh? ¿Quién quiere hacer lo mismo con JSP o JAXB?

Desde otro punto de vista, ya sea usando DSL externos o internos, una de las cosas más interesantes es hacer DSL orientados a negocio. Esto puede ser un paso adelante a la hora de solucionar un antiguo problema del software: la mala comunicación entre usuario y desarrollador. Tradicionalmente se ha usado al analista de negocio para cubrir ese hueco, pero en la práctica los problemas de comunicación entre el analista de negocio y el desarrollador no son despreciables. Si pudiéramos escribir los requisitos de negocio, en un lenguaje de programación, pero que a la vez fuera legible por un cliente o en su defecto, el analista de negocio, se ahorrarían muchos malentendidos. Un opción muy de moda es el modelado visual, pero claro, será visual, pero sigue haciendo falta la habilidad de modelar formalmente, habilidad que no tienen muchos clientes y expertos de negocio. Ellos saben de su negocio, pero no necesariamente saben plasmarlo en un modelo visual. Otro problema es que si bien algunos dominios de negocio se prestan muy bien al modelado visual, otros no. ¿Cómo modelamos visualmente un portfolio de acciones y derivados? Finalmente, para modelar visualmente, suele ser necesaria una herramienta, normalmente cara. En este sentido los DSL son superiores a las herramientas de modelado visual. Por un lado, el DSL está desarrollado específicamente para el dominio de negocio, con lo que son fáciles de usar para definir el negocio. Por otro lado, no se necesita una herramienta visual. Y finalmente, no se pretende que el experto de negocio escriba el DSL, sólo que sea capaz de leerlo, entenderlo y validarlo, que es lo realmente importante, escribir código ya lo hacemos nosotros.

Veamos algunos ejemplos de DSL de negocio (seguimos con Groovy, ¡cómo me gusta!):

  • Manejemos algo de dinero:
    def account=people
      .find(id: '22')
      .defaultAccout
    if(account.balance > 4000.pound + 30.dollar)
      account.transferTo(
        accountId:'24',
        amount: 20.euro + 30.dollar - 4.pound)
  • Ya que tenemos dinero, podemos invertir en bolsa:
    people.find(id:'0345')
      .buyOffer
      {
        buyPosition(desired: 35.ibm, maxPrice:34.euro)
        buyPosition(desired: 2.google, maxPrice:43.euro)
        buyPosition(desired: 10.microsoft, maxPrice:15.euro)
      }

Por supuesto, no soy experto en este tipo de negocios, así que alguna barbaridad habré puesto. Pero coincidireis conmigo que trabajando en equipo con un experto de negocio puede salir algo realmente potente, que nos permita definir test de aceptación, reglas de negocio y validaciones, de forma sencilla y legible.

El hecho de usar DSL para agilizar la comunicación con el cliente produce una sinergia con el uso de TDD, BDD y MDD (Model Driven Development). En TDD y BDD, podemos definir los tests de aceptación directamente en nuestro DSL de negocio, con lo que las pruebas serán más fáciles de escribir y validar. Desde el punto de vista de MDD, el DSL de negocio no es más que una API montada sobre el modelo OO de negocio, que nos permite construir modelos de negocio usando un lenguaje cercano al negocio. O sea, si usamos MDD, deberíamos aprovechar el modelo de negocio como base para nuestro DSL, de hecho podríamos considerar el DSL como un API builder para construir instancias de modelos de negocio y operar con ellos.

Un último detalle, algunos pensareis que pongo demasiado énfasis en la legibilidad del código, pues bien, el caso es que el código se lee mucho más frecuentemente que se escribe. La lectura y comprensión del código es una acción más frecuente que la escritura de este, y por lo tanto prefiero favorecer la legibilidad con respecto a la escritura. Si lo pensáis bien, este es la razón de que cosas como la generación de código y los wizards, al final no suelan ser muy útiles en lo que realidad importa, el mantenimiento correctivo y evolutivo de la aplicación. En esto los DSLs dan una ventaja grande, ya que proporcionan legibilidad y a la vez suelen simplificar la escritura.

Guau, menos de tres mil palabras, ¡ todo un récord ! Buenas noches.

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 »