Microservicios ¿para qué y cuándo usarlos?

“The ability of your business to change quickly, innovate easily, and meet competition wherever it arises is a strategic necessity today” – Mulesoft.

En esta entrada de blog vamos a hablar acerca de la arquitectura de microservicios y el porque cada ves más las empresas buscan tener soluciones tecnológicas basadas en esta arquitectura que sin duda esta tomando más relevancia en la industria en los últimos años. ¿Qué hace de esta arquitectura una manera de agilizar la TI de la empresa respecto a los objetivos estratégicos de la misma? Ahora lo veremos.

¿Qué son los microservicios?

Son soluciones enfocadas en el negocio que responden al cambio de manera ágil. Ya que cada pieza de software es autónoma, gobernada de manera independiente y fácil de escalar por lo que reaccionar antes los cambios del mercado resulta más eficiente que trabajar de la manera convencional; además los microservicios son piezas de software que se entrelazan para brindar lógica a soluciones de negocio existentes y futuras dando así un mayor retorno de inversión (ROI) para la empresa.

Los microservicios (los cuales encapsulan funciones de negocio) deberían considerarse activos digitales ya que por cada microservicio que se desarrolle dentro de la empresa se esta generando funcionalidad valiosa que se puede reutilizar después en otros contextos para resolver problemáticas nuevas y de esta manera no tener que escribir una y otra vez software para solucionar temas similares. De esta manera el área de TI debe ser capaz de facilitar nuevas capacidades de negocios (cambios) reutilizando estos activos digitales.

Orígenes de los microservicios

Todo nace de hace algunas décadas donde los componentes de software se solían empaquetar en soluciones monolíticas. Todos los módulos tanto los componentes de interfaz gráfica, componentes de negocio, componentes de interacción con base de datos, etc. se empaquetan en un mismo ejecutable el cual se solían desplegar en un servidor de aplicaciones como JBoss o WebSphere.

Algunas desventajas de esta arquitectura monolítica tradicional:

  • Difícil de mantener, todo el código base se suele encontrar junto.
  • El escalamiento se logra creando copias de toda la aplicación.
  • Un error en un módulo dado puede comprometer todo el sistema.
  • Es complicado adoptar nuevas tecnologías, un cambio tecnológico suele afectar la aplicación completa. 

Después vinieron modelos más sofisticados como es la Arquitectura SOA. En cierto modo, los microservicios son la evolución natural de las arquitecturas orientadas a servicios (SOA). La filosofía de SOA es reutilización de componentes de software. Pero hay diferencias importantes entre los microservicios y estas arquitecturas.

Algunas de las desventajas de SOA:

  • Se solían ocupar metodologías waterfall (aún no se utilizaban las metodologías ágiles de hoy en día como scrum).
  • Se solían desplegar las operaciones y servicios como un solo ejecutable, se debía hacer una evaluación de regresión a todo el sistema al hacer un cambio.
  • Al hacer actualizaciones todo el sistema resultaba afectado y debía darse de baja.
  • En las soluciones SOA se solía utilizar SOAP para implementar servicios web.
  • Las arquitecturas SOA solían utilizar Enterprise Service Bus (ESB) para las comunicaciones lo cual hacia la solución más compleja.
  • Las arquitecturas SOA solían compartir una sola base de datos RDMS.
  • Las arquitecturas SOA eran difíciles de escalar ya que se escalaban sistemas completos.

La arquitectura de microservicios vienen a resolver muchas de esas desventajas de monolítico y de las implementaciones fallidas de SOA ya que se aprendió del pasado a continuación ahondaremos un poco más en el origen de los microservicios.

Origen de la palabra microservicio

El término “microservicio” se utilizó por primera vez a mediados de 2011 en un evento de arquitectos de software sobre tecnologías cloud. En 2012, varias compañías de TI (principalmente Netflix y Amazon) comenzaron a considerar la idea de los microservicios, y para 2014, las grandes referencias de TI habían comenzado serias discusiones sobre la inversión en ellos para el futuro.

Pero ¿cómo se consumen estos servicios?

Técnicamente hablando un microservicio es un componente de software que expone su funcionalidad regularmente por medio de la arquitectura REST, el miscroservicio se encuentra alojado ya sea algún lugar de la red, en la nube corporativa o pública de la empresa.

Como cualquier otro servicio REST su contrato se puede definir utilizando algún lenguaje de modelado de APIs REST como RAML o Swagger para hacer la definición formal del contrato. Finalmente la forma de comunicarse al servicio es de manera síncrona o asíncrona y dependiendo de la forma de comunicarse con el es la tecnología a utilizar.

Para un consumidor final es más común que la comunicación con el microservicio sea mediante el protocolo HTTP de manera síncrona y además no llamando de manera directa al servicio sino usando un componente intermedio llamado API Gateway del cual hablaremos más adelante. Los microservicios pueden comunicarse entre sí de forma nativa debido a la adopción en la industria de estándares como HTTP y JSON. En otras palabras, son intrínsecamente interoperables pero también pueden existir otro tipo de comunicación entre ellos, como es la comunicación asíncrona utilizando mecanismos de coreografía; como se trata de una comunicación basada en mensajes, el microservicio cliente asume que la respuesta no se recibirá inmediatamente y que es posible que no haya ninguna respuesta.

Usar la tecnología de los microservicios lo más agnóstico posible

En el mundo de la tecnología las cosas cambian a cada minuto, así que nuevos lenguajes, frameworks y herramientas están saliendo todo el tiempo. ¿Qué pasa si con el tiempo queremos experimentar con una nueva tecnología que resuelva las cosas en menor tiempo y hacernos más productivos? En el mundo de los microservicios esto significa que debemos usar tecnología de integración lo mas estándar posible y no casarla a una tecnología de implementación dada, por lo tanto es recomendable usar tecnología de integración estándar como REST, HTTP y JSON y tampoco casarnos con un lenguaje de programación dado o algún proveedor de servicios cloud particular.

Arquitectura de microservicios

El sitio microservices.com propone el siguiente modelo de referencia de la arquitectura de microservicios, las empresas que han adoptado con éxito los microservicios han usado una serie de patrones arquitectónicos comunes. Esta arquitectura de referencia de microservicios ya la han usado muchas compañías y esta relación entre los componentes del sistema se ha documentado con algunos de los patrones de diseño principales que se han identificado con el paso del tiempo implementando estas arquitecturas.

Reference Architecture Diagram
Arquitectura de referencia para microservicos

Algunos de los componentes a destacar en la arquitectura de referencia son:

A. Discovery: El descubrimiento de servicios asigna nombres de servicios lógicos a direcciones físicas. Los microservicios utilizan el descubrimiento de servicios para encontrar la dirección física de un servicio determinado, ya que podemos tener cientos de servicios o más desplegados en nuestro sistema es necesario tener identificadas las direcciones e IP’s de nuestros microservicios para lograr una organización interna mucho mejor.

B. Microservices: Los microservicios generalmente se implementan en varias instancias para disponibilidad y confiabilidad. Cada microservicio contiene una biblioteca local que controla cómo los microservicios se comunican entre sí.

C. Control layer: Cada microservicio envía y recibe metadatos: datos de latencia, datos de registro, información de enrutamiento, etc. Todos estos metadatos se comparten en una capa lógica llamada Capa de control.

F. Applications: Típicamente escritas por terceros, las aplicaciones realizan funciones especializadas como persistencia o búsqueda. Las aplicaciones suelen tener entradas estáticas en el descubrimiento de servicios y pueden estar detrás de un balanceador de carga.

Para tener una descripción más completa de esta arquitectura de referencia, visitar el sitio microservices.com

Clasificación de los microservicios

Cada empresa puede realizar la clasificación de los microservicios según los estándares internos de la empresa o siguiendo modelos mas generales a nivel industria. Por ejemplo la empresa Mulesoft impulsora de tecnologías como REST y RAML propone la siguiente clasificación:

En la capa de System API’s es donde se definen microservicios de más bajo nivel para gestión directa de entidades de negocio como Cliente, Pedido o Factura. Estas API del sistema están en línea con el concepto de un servicio autónomo que ha sido diseñado con suficiente abstracción para ser agnóstico a cualquier proceso de negocio y típicamente son usadas para hacer operaciones de tipo CRUD sobre una entidad sin tener lógica de negocio asociada o si acaso con lógica muy básica.

Las API del sistema se pueden componer con otras API para formar un agregado, a esta capa se le conoce como Process APIs. La composición de las API del sistema puede tomar la forma de orquestación explícita (llamadas directas) o mediante coreografía (llamadas asíncronas), todo esto para satisfacer un requerimiento de negocio mediante composiciones, por ejemplo para el cumplimiento de pedidos, alta de un cliente en una empresa, hacer un traspaso bancario, etc.

Tanto las API del proceso como las del sistema deben adaptarse de cara al consumidor y a las necesidades de cada canal de negocios y
punto de contacto digital.
Un mecanismo de seguridad particular podría ser necesario en alguno de los canales consumidores; el patrón API Gateway es un buen enfoque porque es donde están estas particularidades están configuradas, aspectos como seguridad, logging, auditoria y el filtrado de datos se configuran en esta capa.

¿Qué tan grandes o pequeños son los microservicios?

La palabra micro apunta a algo muy pequeño y conciso, dado esto ¿Un microservicio se mediría por la cantidad de líneas de código? ¿Por su complejidad ciclomática? ¿Por su granularidad?

Aunque la palabra micro apunta a algo muy pequeño en el tema de los microservicios no siempre es así ya que existen servicios de diversos tamaños, la palabra micro tiene que ver con que el microservicio tiene una interfaz pública o contratomicro“.

Proporciona la menor cantidad posible de operaciones públicas, literalmente, un servicio “micro”. Ejemplo:

Alta cohesión

Si queremos cambiar el comportamiento de algo es mejor que ese algo se encuentre en un mismo lugar, hacer cambios en un montón de lugares distintos puede resultar lento y riesgoso y es por esto que debemos evitar tener una misma funcionalidad regada en diversos servicios por lo tanto es recomendable tener juntas las operaciones que tienen que ver entre si y encontrar las fronteras adecuadas de nuestro microservicio con otros servicios del sistema.

Identificando candidatos a microservicios

El más obvio candidato a microservicio es aquella entidad que representa algo para el negocio, ejemplo para un banco podemos tener un microservicio que tenga que ver con las operaciones de tarjetas de crédito y otro microservicio para tarjetas de débito. Otro ejemplo es la visita que hace un  paciente a un hospital podría ser gestionada por un servicio de gestión de consultas que a su vez hace uso de servicios de más bajo nivel, por ejemplo de un servicio que gestione pacientes y de otro que gestione a los médicos:

También es importante definir la granularidad de cada operación del microservicio. Mientras la granularidad sea mas gruesa mas responsabilidades tendrá una sola operación, mientras la granularidad sea mas ligera la responsabilidad será mas especifica. En este caso hay que tener cuidado de no caer en antipatrones como el conocido como ‘nanoservicio’ en el que las operaciones de un microservicio son demasiado especificas a nivel de dato de una entidad, por ejemplo un servicio de gestión de clientes que tuviera la siguiente operación demasiado especifica ‘setCustomerName’.

Diferencias entre API y Microservicios

  • El API es una interfaz que expone un grupo de operaciones a un consumidor mientras que un microservicio puede ser uno de esos endPoint del API.
  • El API es el contrato para interactuar con el microservicio el cual es la pieza de implementación de todo o parte de ese contrato. 

Principios de Diseño de Servicios

Existen algunos principios de diseño de servicios sugeridos por Thomas Erl en su libro “SOA Principles of Service Design” los cuales son aplicables a la arquitectura de microservicios como:

  • Standardized Service Contract: Contrato estandarizado para todos los microservicios.
  • Reusability: Piezas reusables y adaptables a diferentes contextos dentro del negocio.
  • Las dependencias entre el servicio y su entorno es minimizado aplicando el principio de Loose coupling, los cambios que se apliquen a loa lógica interna del servicio deben afectar lo mínimo o nada a los consumidores.
  • Autonomy, característica de runtime que promueve la mejora de escalabilidad, performance y disponibilidad, los cambios a un servicio por ejemplo el deploy de este no tiene que afectar a otros servicios.
  • Composability, permite a cada servicio entregar valor en diferentes contextos.
  • Discoverability, permite que el servicio esté publicado en un ‘registry’ para exponer lo necesario para ser consumido.

Recomendaciones finales

Es altamente recomendable hacer un análisis para saber si nuestro proyecto es candidato para usar arquitectura de microservicios.  No todos los proyectos son candidatos para utilizar esta arquitectura, por ejemplo si estamos trabajando en un proyecto para resolver una necesidad temporal o en un proyecto pequeño tal vez no sea necesario invertir en una arquitectura compleja de microservicios, recordemos que la arquitectura si bien nos da muchos beneficios también introduce complejidad al estar trabajando en un sistema distribuido.​

La adopción de la arquitectura de microservicios no es simplemente una decisión técnica, también tiene un impacto cultural en la organización. No todas las organizaciones están listas para capitalizar esta arquitectura.

Si ya decidimos que los microservicios son para nosotros entonces sería recomendable analizar los flujos de datos y actividades dentro del sistema, identificar la actividad tanto síncrona como asíncrona. Analizar los ‘paths’ de flujo de la información, este ‘aproach’ es bueno para descomponer el sistema en mensajes y a priori comenzar a identificar potenciales servicios. 

Lejos de que se llegue a un acuerdo del significado de la palabra microservicios tenemos preferiblemente que reflexionar acerca del beneficio que nos da utilizarlos. Así que ¿En qué casos de uso da más valor utilizar microservicios? Yo diría que en aquellos donde queremos tener la habilidad de tener ciclos altos de velocidad de entrega de funcionalidad/software (faster time to market) y en aquellos casos en donde la estrategia de la empresa apunte a desarrollar piezas de software reutilizable que potencialmente pueda usarse para resolver problemas similares a futuro con un alto nivel de ROI (retorno de inversión).

El implementar soluciones con microservicios también conlleva costos:

  • El cómputo distribuido siempre viene acompañado de complejidad (latencia, escalabilidad, incremento de tráfico en la red, únicos puntos de falla, etc).
  • También se debe contar con soluciones robustas de integración continua y despliegues automáticos de software para reaccionar rápido ante los cambios.
  • Las pruebas se complican en el modelo distribuido.

La mayoría de las organizaciones tienen una base de código ‘legacy’ monolítico existente y están migrando poco a poco funcionalidad a microservicios de forma incremental así que no se trata muchas veces de adoptar la tecnología de golpe, sino seguir una estrategia en fases la cual es una buena forma de adoptar gradualmente el cambio de paradigma.

Aquí concluimos con este resumen de lo que es la arquitectura de microservicios y el porque es una apuesta solida en el presente y futuro de la creación de activos de negocio reutilizables. Nos volvemos a encontrar próximamente en más apasionantes temas de tecnología.

Referencias

“Ingeniería de Software” como antecedente de Agile

Introducción

Si ya tienes conocimientos de Scrum puedes adelantarte esta sección, si apenas estas empezando a investigar al respecto, sigue adelante con esta breve introducción.

Actualmente, y desde hace unos meses en México se percibe un crecimiento por el interés en Scrum, algunas empresas y algunas personas no saben que criterios utilizar para elegir la entidad certificadora y basamos (me incluyo) nuestra decisión en lo que solemos escuchar en la oficina o con nuestros amigos involucrados en el medio.

Un poco de historia.

Si, historia, aunque no todos estemos atraídos por la historia, en este caso es necesario para entrar en contexto, no te preocupes, trataré de no aburrirte con fechas.

Tenemos que poner nuestro punto de partida en el siglo pasado, a finales de los 50’s y principios de los 60’s cuando hubo un “boom” en la demanda de profesionales que pudiesen programar software, la industria no sabía como debía manejar esta nueva “cosa” intangible (software) que montábamos sobre mecanismos tangibles (hardware) que eran bastante robustos y costosos.

EL IBM 650 – Imagen tomada de http://yofuiaegb.blogspot.com

El ser humano en su naturaleza por organizar y definir formas para que el desarrollo de software estuviese estandarizado reunió a un grupo de expertos-no_expertos, — les llamo Expertos, debido a que eran quienes estaban construyendo software, eran los representantes máximos de diferentes naciones en temas de computación, no había nadie más. Les llamo No_Expertos dado que en aquellos tiempos no existían las carreras de ingeniería de software y/o informática y/o computación que hoy en día tenemos–.

Este grupo se reunió para que definir como debíamos de construir software. El término ingeniería de software se mencionó por primera vez en dos conferencias realizadas en la OTAN en 1968 y 1969 (aquí te dejo el link por si quieres darle un vistazo), en dicha conferencia el tema fue la “crisis de software” y se buscó un enfoque de ingeniería que buscaba reducir costos y llevar a un software más confiable.

Los programadores tomaban como referencia otras áreas de la ingeniería, como la Civil, Mecánica y/o Eléctrica, incluso la habían ejercido de tal manera que la ingeniería de software era algo completamente nuevo.

Lo anterior derivó en una definición de ingeniería de software que trataba de emular-imitar otras ingenierías, ¿te suena el rol de Arquitecto en el ramo de la construcción como en el ramo de tecnología?¿o qué me dices de los planos de un edificio versus los planos escritos en lenguaje unificado de modelado (UML)?

Fue así, como se impulsó una forma de construir software mediante la definición de un modelo altamente predictivo y secuencial que hoy en día conocemos como Cascada o Waterfall (por el inglés), y que puedes certificarte con una institución internacional llamada PMI (Project Management Institute)… Qué por cierto, si un día te presentas en uno de estos cursos te encontrarás a más de uno que no viene de TI.

The NATO Software Engineering Conference
Comité de Ciencias de la OTAN, Rome, Italy, 27-31 Oct. 1969 – Imagen tomada de GitHub.

Una manera diferente de hacer las cosas.

Como platicábamos en aquellos tiempos, de común acuerdo todos empezaron a desarrollar software de manera predictiva utilizando cascada, ¿el resultado?, empezaron a surgir más y diferentes problemas en la industria del software, durante las décadas de los 70’s y 80’s, ¿Cuáles eran?

  • Desborde de presupuesto. Tratar de predecir el proyecto desde un inicio llevaba a no concluir en tiempo, y por lo tanto, elevaba los costos anticipados del proyecto, de hecho, eso aún pasa en muchas empresas en México.
  • Liberación de software defectuoso. Por tratar de liberar a producción en tiempo y forma se descuidaban actividades de aseguramiento de calidad que provocaban liberaciones defectuosas, y por consiguiente, el esfuerzo por “parchar” dichos defectos en siguientes versiones. Creo que esto también esta vigente en nuestro país y en algunos otros.
  • Funcionalidades inútiles. Hablamos de funcionalidades que el usuario final nunca utilizó, que a alguien se le ocurrió que eran buena idea, las metió en el documento de especificación de requerimientos, el equipo de desarrollo las construyó y el equipo de pruebas ejecutó diversos escenarios para asegurarse de que la implementación fuera correcta, ¿Alguna vez has usado todas las características de las aplicaciones instaladas en tu smartphone?

Dado lo anterior surgieron pensamientos e ideas para construir software fuera del canon, algunas de estas personas incluso se sintieron “sucios” por no tener hermosos diagramas de Gantt y no estar diseñando los planos de sus sistemas mediante elegantes diagramas de UML, la razón, no tenían tiempo, urgía más construir software antes que pasar por las etapas de análisis y diseño.

Entre este grupo de personas (ubicados en diferentes zonas geográficas), se encuentra una persona de nombre Jeff Sutherland, quien en conjunto con Ken Schwaber, empiezan a dar forma a sus ideas después de leer un artículo publicado en 1986 en la revista de Harvard: The new new product development game, ideas que después se plasmarían en el Framework de Scrum.

hirotaka-takeuchi-ikujiro-nonaka1
Nonaka y Takeuchi (1995) – Imagen tomada del blog de Romero Richard.

El 2001 y el manifiesto ágil.

Como mencionábamos, Jeff Ken, junto con otro grupo de personas sabían que se debía encontrar una forma diferente de desarrollar software, una forma en la cual se evitaran los problemas que todos los proyectos a nivel internacional estaban presentando, una manera a la que posteriormente le llamarían “ágil”.

¿Quiénes se encontraron entre febrero 11 y 13 de 2001 para escribir y firmar dicho manifiesto?, solo por mencionar a algunos de ellos:

  • Jeff Sutherland. Uno de los creadores de Scrum.
  • Kent Beck. Uno de los creadores de la Programación Extrema y el Desarrollo Orientado por Pruebas.
  • Ken Schwaber. Co-creador de Scrum.
  • Martin Fowler. También co-creador de la Programación Extrema y experto en análisis y diseño orientado a objetos con UML.
  • Mike Beedle. Doctor en física y autor del primer libro de Scrum y el primero documento publicado sobre Scrum.
  • Robert C. Martin. También se le conoce como Uncle Bob , incluso una de sus empresas tiene el mismo seudónimoEs co-autor de diversos libros, por mencionar The Clean Coder, Clean Architecture.
  • Ward Cunningham. El tercer co-creado de la Programación Extrema, también creo el primer sitio Wiki.

Si quieres revisar la lista completa, aquí te dejo el sitio oficial y aquí esta otro enlace con un breve biografía de cada uno.

History: Some pictures and PDFs of the Agile Manifesto meeting on 2001 | by  Prathan D. | Siam Chamnankit Family
Reunión del Manifiesto Ágil, lado izquierdo de la sala – Imagen tomada del blog en Medium de Siam Chamnankit
Image for post
Reunión del Manifiesto Ágil, lado derecho de la sala – Imagen tomada del blog en Medium de Siam Chamnankit.
Image for post
El borrador del Manifiesto Ágil para el Desarrollo de Software en la pizarra – Imagen tomada del blog en Medium de Siam Chamnankit.

¿A qué va todo lo anterior?

Ya que estamos en contexto, a partir de este momento, en el futuro te platicaremos de Scrum, eXtreme Programing, Kanban y el mundo relacionado al desarrollo de productos de software bajo el enfoque Agile.

https://scrumorg-website-prod.s3.amazonaws.com/drupal/inline-images/2017-05/ScrumFrameworkTest.png
Scrum Framework – Imagen tomada de scrum.org

¿Te gustaría conocer más y certificarte en el camino? Te invito a que me acompañes a mi siguiente curso de certificación, escríbeme a michel.perez@fractal-ti.com.

Buenas prácticas de diseño de APIs RESTful: Nombrado de recursos

Introducción

En el artículo anterior vimos de manera introductoria lo que es REST y algunos conceptos base relacionados. El objetivo de este artículo es presentar una serie de recomendaciones y buenas practicas, algunas de ellas obtenidas de libros y otras en base a algunas
de las situaciones a las que me he enfrentado definiendo los nombres de los recursos en APIs REST de manera profesional.

Para algunos el nombrado del recurso es una tarea trivial, para mí requiere principalmente del entendimiento correcto del negocio que se pretende modelar y en algunos casos se requiere de experiencia, así que con la práctica aprenderemos a definir los nombres correctos.

Recapitulando un poco del artículo anterior, en la arquitectura REST el recurso lo es casi todo pues la base de como el cliente interactúa con el servidor es a través de recursos REST pero entonces ¿qué es un recurso?.

Un recurso en el contexto de REST es una representación de una entidad que existe en el servidor, el recurso es una estructura de datos que se identifica de manera única mediante su Uniform Resource Identifier (URI). Es algo así como un registro de base de datos el cual identificamos mediante una llave primaria única.

Pero mejor exploremos este concepto con un ejemplo:

http://my-domain.com/my-films-api/v0/films

En el ejemplo de arriba definimos un recurso el cual llamamos ‘films’ el cual representa una colección de películas. Como se puede ver el recurso está representado mediante una URI. Ahora bien aquí entra otro concepto clave, JSON. JSON  (JavaScript Object Notation) es la notación comúnmente usada por su sencillez para representar el estado actual de un recurso, para nuestro ejemplo el servidor podría regresar la representación de una película dada mediante está notación de la siguiente manera:

{
   "data": [
     {
       "filmId": "67490436a8be5153fe7d8bc54344fa1e",
       "name": "The Lord of the Rings: The Return of King",
       "duration": "3h 21m",
       "principalCast": [
         {
           "name": "Elijah",
           "lastName": "Wood",
           "actorId": "60490436a8be5153fe7d8bc54344fa78"
         }
       ]
     }
   ]
 }

Ahora que ya hablamos acerca de que es un recurso a continuación comentaremos de como nombrar el recurso de manera adecuada. Existen algunas recomendaciones base para el nombrado de recursos, en la práctica algunas veces no resulta tan sencillo sobre todo en aquellas situaciones donde cuesta trabajo encontrar el sustantivo adecuado que represente a la funcionalidad a modelar. Hay casos que son fáciles de identificar pero en otros casos no es tan obvio.

Nombrado en sustantivo y en plural

Como primera recomendación es que el recurso REST se define siempre en plural y el nombre que decidamos poner al recurso debería representar un sustantivo dentro del contexto funcional del API, por ejemplo para el caso de un API que gestiona información de películas podríamos tener un recurso llamado ‘films’ y para la gestión de actores uno que se llame ‘actors’, siempre definiendo sustantivos.

Ojo que no siempre la definición de un recurso hace necesariamente match con la definición de las tablas de base de datos del backend, podríamos caer en la trampa de definir los recursos a como se llaman las tablas en la base de datos. Por ejemplo podemos tener un recurso que se llame ‘directors’ en el API pero esto no significa que tengamos una tabla que se llame así en la base de datos de películas.

Un ejercicio recomendado para el nombrado es encontrar los sustantivos adecuados según el negocio que se esté modelando, por ejemplo para un API que maneje reservaciones de vuelos no sería raro encontrar los recursos ‘air-lines’, ‘reservations’, etc. Para un sistema de carrito de compras seguramente encontraremos los recursos ‘carts’, ‘orders’ y ‘items’.

Saliéndonos un poco del ejemplo de películas ¿Cómo nombraríamos a un recurso que gestiona correos electrónicos? por ejemplo si quisiéramos modelar una operación para mandar un correo nuevo a un cliente. Un primer approach sería definirlo como:

POST http://my-domain.com/my-api/v0/emails

Como primer approach está bien llamarlo así pero igual nos valdría más definirlo como un recurso más genérico para cubrir cualquier tipo de notificación y no solo de correo electrónico sino también de SMS o algún otro, en ese caso la definición la cambiaríamos por algo más general:

POST http://my-domain.com/my-api/v0/notifications

Los métodos HTTP

Los métodos HTTP nos dicen el significado de la acción a realizar hacia el recurso REST a continuación una lista para darnos una idea del uso más común de cada método.

  • GET es comúnmente usado para hacer consultas de un recurso ya sea para obtener una lista de recursos de un mismo tipo o para obtener el detalle de un recurso mediante su identificador único.
  • PUT es comúnmente usado para crear o actualizar un recurso.
  • PATCH es comúnmente usado para actualizar un recurso de manera parcial.
  • HEAD es comúnmente para verificar si un recurso existe o no en el servidor.
  • DELETE es comúnmente usado para eliminar un recurso de manera física o incluso lógica.
  • POST es comúnmente usado para crear un recurso nuevo dónde el ID del recurso es generado de manera automática por el servidor. También se usa el método POST para ejecutar una acción usando el patrón ‘Controller’ (el cual describiremos a continuación).

Categorías de recursos

Algunos autores de libros sobre REST han identificado patrones o clasificaciones de recursos, por ejemplo en el libro RESTful API Design: Best Practices in API Design with REST se destacan los siguientes.

Son de tres categorías:

  • Collection
  • Instance/Document
  • Controller

* Existe una cuarta categoría llamada Store el cual no abordaremos en este momento.

Veamos a continuación el detalle de cada uno de ellos.

Collection

Es un directorio de recursos gestionado por el backend. Los clientes generalmente consultan el directorio y solicitan al backend que se agregue un nuevo recurso al directorio.

Métodos HTTP dónde se usa el patrón: GET y POST.

El endpoint que devuelve una lista con todas las películas de un cliente se considera como un recurso de tipo de Collection.

Ejemplo de URI para obtener una lista de películas:

GET http://my-domain.com/my-films-api/v0/films

Obtener los directores asociados a una película:

GET http://my-domain.com/my-films-api/v0/films/123434/directors

Crear una nueva película:

POST http://my-domain.com/my-films-api/v0/films

Instance/Document

Representa una instancia de un recurso de negocio, una instancia es identificada por un ID único. Por ejemplo:

Obtener una película en base a su ID:

GET http://my-domain.com/my-films-api/v0/films/{film-id}

Actualizar todos los atributos de una película:

PUT http://my-domain.com/my-films-api/v0/films/{film-id}

Actualizar uno o algunos de los atributos de una película:

PATCH http://my-domain.com/my-films-api/v0/films/{film-id} 

Eliminar una película:

DELETE http://my-domain.com/my-films-api/v0/films/{film-id}

Controller

Se utiliza para representar acciones que no necesariamente en todos los casos modifican el estado de un recurso en el servidor.

En RESTful modelar la URI como recurso a modo de sustantivo es la manera adecuada para representar las funcionalidades del API y las acciones se identifican por el método de HTTP (GET, PUT, PATCH, HEAD, POST, DELETE) pero algunas veces es complicado modelar una funcionalidad de esta manera algunos ejemplos de su posible uso:

  • Si queremos modelar la ejecución de un proceso en batch
  • Si queremos realizar alguna validación del lado del servidor por ejemplo validar si un correo electrónico de un cliente está vigente o no.
  • Si queremos resolver alguna tarea que resulte difícil modelar mediante una entidad de negocio, comúnmente aplicado para resolver tareas técnicas por ejemplo migrar registros de una base de datos a otra.

En este caso la funcionalidad se podría modelar como un patrón ‘Controller’ además para este caso se recomienda usar siempre el método POST y al nombrar al recurso siempre se hará como acción o verbo.

Algunos ejemplos:

Simular el otorgamiento de un crédito bancario. Lo haremos mediante la definición de un recurso que nos represente préstamos el cual llamaremos ‘loans’ y del cual cuelga una acción llamada (como verbo) ‘simulate’.

POST http://my-domain.com/my-api/v0/loans/simulate

Otro ejemplo: ejecutar un proceso batch que elimina clientes duplicados.

POST http://my-domain.com/my-api/v0/customers/remove-duplicates

Como nota final de este patrón he de decir que hay que ocuparlo con reserva y solo en caso de que la funcionalidad deseada no pueda ser modelada con alguno de los otros patrones.

Granularidad de un servicio

La granularidad de un servicio REST se puede medir en varias dimensiones, una de esas dimensiones es la responsabilidad funcional que tiene el servicio y esa granularidad puede influenciar al nombre del recurso.

La responsabilidad funcional que tiene cada operación del servicio puede ser de granularidad gruesa o delgada y lo voy a poner más claro con un ejemplo.

Si tenemos una operación que da de alta películas se podría modelar de diversas maneras (dependiendo de la responsabilidad funcional). Si quiero cubrir todo tipo de películas (grano grueso) entonces lo que más conviene es modelar la operación así:

POST http://my-domain.com/my-films-api/v0/films

Pero podríamos modelar esa misma operación de manera más específica dividiéndola en endpoints más específicos (granularidad delgada), bien podría definir un endpoint particular según el tipo de película como a continuación se ilustra:

POST http://my-domain.com/my-films-api/v0/ci-fi-films
POST http://my-domain.com/my-films-api/v0/action-films
POST http://my-domain.com/my-films-api/v0/adventure-films

¿Qué nos conviene más entonces? ¿Tener un solo recurso que de alta todo tipo de películas o tener diversificados los recursos por tipo de película?.

Para esto se tendrá que hacer una evaluación entre granularidad fina y gruesa y asociarlo a nuestra necesidad, en un siguiente artículo hablaré de la granularidad de un servicio REST así como pros y contras de granularidad fina vs granularidad gruesa para así poder llegar a entender cuál de los dos modelos se adaptan mejor a nuestra necesidad particular.

Identificar el recurso dependiendo del objetivo del servicio

Otra forma para darle un buen nombre a un recurso es hacernos varias preguntas acerca de la funcionalidad que estamos modelando, preguntas como:

  • ¿Quién usará el servicio?
  • ¿Cuál es el objetivo del servicio?
  • ¿Qué datos de entrada se esperan recibir y que datos de salida se espera regresar?
  • ¿Internamente el servicio qué hace? ¿guarda, consulta, actualiza, calcula?

Estas preguntas nos van a permitir identificar acciones y recursos dentro de nuestro contexto de negocio, algunos ejemplos:

  • Buscar productos en el catálogo.
  • Actualizar una tarjeta de crédito en la lista negra.
  • Actualizar el precio de un producto.
  • Procesar una solicitud de crédito.

Para nuestra API de películas ¿Cómo podríamos modelar el recurso que nos permita asociar una imagen a una película?.

Para empezar la acción a realizar es un alta por lo que lo mas adecuado sería utilizar un método POST (aunque aquí ojo el método PUT también puede ser usado para crear un recurso nuevo solo que tiene diferencias sutiles con POST).

En la frase “Asociar una imagen a una película” encontramos dos posibles sustantivos ‘films’ e ‘images’. Por lo que un posible diseño de recurso para este caso podría ser:

POST http://my-domain.com/my-films-api/v0/films/t5t5ggtt5/images

En nuestro ejemplo tenemos un recurso llamado ‘films’ el cual tiene un identificador único ‘t5t5ggtt5’ el cual tiene un subrecurso asociado llamado ‘images’. Finalmente la acción la representamos con el método http POST que en este caso nos representaría en una creación de un recurso ‘images’ nuevo.

Ahora que ya sabemos como nombrar un recurso en base a su contexto funcional hagamos un ejercicio más:

Diseñar el recurso de la operación que crea una solicitud de crédito para auto. ¿Cuál de las siguientes opciones es la más correcta para tal fin?

  1. POST http://my-domain.com/my-api/v0/CarCreditLine/create
  2. GET http://my-domain.com/my-api/v0/car/credit
  3. POST http://my-domain.com/my-api/v0/car-credit-applications
  4. POST http://my-domain.com/my-api/v0/create-car-credit

Para este ejemplo la opción más a adecuada según las buenas prácticas y lo más apegado a RESTful es usar la opción 3.

Recursos y Anti-patrones

A continuación mostraré una lista de ejemplos que nos describen algunas malos ejemplos en el nombrado de recursos:

GET http://api.example.com/services?op=update_customer&id=12345&format=json

En el ejemplo anterior el recurso REST se nombra como ‘services’ un nombre demasiado genérico y la acción a realizar se envía como query param ‘op=update_customer’. Las acciones a realizar en REST se representan por los métodos de http GET, PUT, POST, etc. y no deben representarse mediante el uso de query params.

Veamos otro ejemplo:

GET http://api.example.com/update_customer/12345

En este caso si la acción a realizar es actualizar un cliente lo mas adecuado sería usar un método de http PUT o PATCH y no GET por lo que resultaría más adecuado definir el endpoint como:

PUT http://api.example.com/customers/12345

Otro ejemplo más:

GET http://api.example.com/customers/12345/update

En el ejemplo anterior estamos definiendo una acción la cual llamamos ‘update’ pero la estamos invocando con un método GET lo cual es incorrecto.

Y un ejemplo más:

GET http://www.example.com/customer/33245

En este último ejemplo el recurso se llama ‘customer’ en singular, cuando la recomendación es que el nombrado se haga en plural como ‘customers’.

Recomendaciones finales

  • No se recomienda usar caracteres mayúsculas y minúsculas juntas en el nombre de recursos por ejemplo del estilo camelCase : ‘CarInvoice’, ‘RootClient’.
  • Para separar palabras se recomienda el uso de guiones medios en lugar de guiones bajos. Ejemplo: ‘car-invoice’ o ‘root-client’.
  • El nombre del recurso debe ser independiente del formato, no debe contener extensiones de archivos por ejemplo: ‘car-invoice.pdf’
  • No usar espacios en blanco para el nombre. Ejemplo: ‘car invoice’
  • Los nombres de recursos no deben implicar una acción, por lo tanto debe evitarse usar verbos en ellos a menos que estemos usando el patrón del tipo ‘Controller’.

Para finalizar recomiendo un libro que a mi me sirvió para profundizar acerca del diseño de APIs Web con REST y donde se explican a mas detalle algunas de las recomendaciones aquí expuestas:

También comparto la referencia al API interactiva de MARVEL la cual tiene buenas prácticas en relación a los nombres de los recursos. Los invito a explorarla para que se den una idea de cómo definieron sus recursos relacionados al universo de los comics:

En mi siguiente artículo hablaré de la granularidad de un servicio, ¿Qué es? y cómo podemos usarla para aplicarlas a nuestras APIs REST.

Hasta la próxima

¿Qué son las APIs REST?

Introducción

Hablando de computación distribuida existen varias formas de comunicar componentes de software a través de la red, hablando más concretamente de APIs Web algunas de estas formas de comunicación populares en la industria son REST, SOAP y más recientemente GraphQL, cada una de esas formas tienen su propio estilo de arquitectura y cada una sigue una serie de principios fundamentales que lo caracterizan de los otros.

Web Services

Un servicio web es un componente de software que expone funcionalidad de valor para el negocio su funcionalidad a través de de internet pero no siempre es así ya que podemos tener servicios web expuestos solo en la red interna de nuestra compañía.

Pero ¿Quiénes son los consumidores de estos servicios web?. Son otros programas de software que fungen con el rol de clientes, el ejemplo más claro es una aplicación móvil que interactúa con un servidor para obtener datos y ejecutar acciones de importancia para el negocio.

En su momento SOAP fue el rey en cuanto a la tecnología para la implementación de servicios web, era el modelo típico de comunicación en arquitecturas SOA clásicas pero con el paso de los años esto ha ido evolucionando de modo que hoy en día REST es más utilizado por su sencillez ya que SOAP es un protocolo más pesado y difícil de manipular.

APIs Web

Un API Web es software o un grupo de componentes que ofrecen una interfaz de comunicación a través de un contrato, ese contrato es expuesto en la red para ser consultado por los consumidores.

El contrato lo que contiene son las operaciones que el API ofrece y también la forma de comunicarse a ese contrato, por ejemplo para un API Web de gestión de películas encontraremos en el contrato operaciones como: dar de alta una nueva película o buscar películas dentro del catálogo. El contrato también nos dice el ¿como? por ejemplo si lo puedo consumir como servicio REST o SOAP o algún otro protocolo de comunicación.

Definición de REST

REST (Representational State Transfer) es un estilo de arquitectura que cumple ciertas restricciones para diseño de servicios web y que nos permite exponer funcionalidad mediante el uso de tecnologías ya conocidas como HTTP, WWW y JSON.

Es una arquitectura ligera y destaca por su sencillez ya que emplea una interfaz web que usa hipermedios para la representación y transición de la información. Cabe destacar que la comunicación entre cliente y servidor es sin estado es decir, toda la información será enviada por el cliente en cada mensaje HTTP, consiguiendo una mayor facilidad en el escalamiento de las soluciones.

Esta arquitectura fue definida por Roy Fielding en el año 2000 para un trabajo de tesis doctoral. El es uno de los autores principales de la especificación HTTP y una autoridad citada frecuentemente en la materia de Arquitectura de Redes.

Frecuentemente se cita la palabra RESTful, este concepto se usa para describir servicios web implementados usando el estilo de arquitectura REST, es ampliamente usado en la actualidad para exponer APIs que son consumidos de manera sencilla sobre todo para frontales de tipo web y móviles. Existen infinidad de empresas que actualmente exponen APIs REST gratuitas y de paga para resolver cualquier cantidad de problemas desde dar el estado del clima hasta hacer pagos bancarios en línea.

Restricciones REST

REST comprende 6 restricciones que lo caracterizan de otras arquitecturas. A continuación las describo brevemente.

Interfaz Uniforme

Todo en REST se basa en recursos, y cada recurso se identifica por un único ID. La manera de identificar recursos es mediante el uso de URI’s (Uniform Resource Identifier), un ejemplo de un recurso podría ser: http://miservidor/mi-api/v0/films/

Un recurso es una estructura de datos simple, pensemos en el como si fuera un registro de una base de datos el cual puede ser identificado mediante una llave, el recurso nos indica solo información general del objeto de negocio pero no ofrece detalles de como se compone ese recurso o que datos lo conforman.

La interfaz uniforme se compone básicamente de 3 partes principales y en su conjunto definen como un recurso es consumido:

  • URI (Uniform Resource Identifier) el cual es una cadena de texto que identifica a un recurso de red.
  • Uso de algún método HTTP como GET, POST, etc. para saber que acción se desea ejecutar sobre el recurso.
  • Un Media Type para identificar el formato en el que viajará la información, normalmente se usa la notación JSON aunque no es el único.

Cacheable

La optimización de la red mediante el almacenamiento en caché mejora la calidad general del servicio de las siguientes maneras:

  • Reducir el ancho de banda
  • Reducir la latencia
  • Reducir la carga en los servidores

Por lo general, los navegadores tratan todas las solicitudes GET almacenables en caché y lo hace mediante el uso de algunos encabezados HTTP. Algunos de esos encabezados son Cache-Control y ETags.

Cliente-Servidor

Los servicios REST se basan en una arquitectura Cliente-Servidor. Un servidor que contiene los recursos y estados de los mismos, y unos clientes o consumidores que acceden a los recursos y los manipulan a través de operaciones de tipo CRUD (Create/Read/Update/Delete).

En este modelo cliente-servidor existe una clara separación de responsabilidades.  El servidor se encarga de las cosas que tienen que ver con el back-end (almacenamiento de datos, reglas de negocio, etc.) y el cliente maneja las cosas de front-end (más relacionadas y cercanas a las interfaces de usuario final).

Sistema en Capas

REST es un estilo de arquitectura en capas ya que puede haber intermediarios entre cliente y servidor los cuales se conocen como ‘Agentes’ que se encargan de
aspectos de tipo ‘cross-cutting’ transversales a todos los servicios, aspectos como: seguridad, trazabilidad, escalabilidad, etc.

De cara al consumidor es transparente la cantidad de capas que componen un sistema REST y estás pueden ser quitadas/sustituidas incluso sin que el cliente consumidor se de cuenta del cambio.

Comunicación tipo Stateless

Está es una de las ventajas más fuertes de los Servicios REST ya que pueden ser escalados hasta alcanzar grandes rendimientos para abarcar la demanda de todos los posibles consumidores. Esto implica implementar mecanismos de escalamiento como balanceos de carga o clusterización por lo tanto es necesario que los clientes REST envíen la información completa e independiente en cada solicitud para que el escalamiento de los servicios sea implementado de manera más sencilla.

Una solicitud completa e independiente no requiere que el servidor mientras procesa la solicitud tenga que almacenar ningún tipo de contexto o sesión y de esta manera resulta ser más fácil de escalar en sistemas de alta demanda. Así que un cliente REST debe incluir dentro de la cabecera y cuerpo HTTP todos los parámetros, contexto y datos necesarios para que el servidor genere la respuesta.

Code on Demand (Opcional)

La sexta y última restricción es la única opcional en el estilo REST.  Code On Demand significa que un servidor puede ampliar la funcionalidad de un cliente en tiempo de ejecución, enviando el código que debe ejecutar (tal como los Applets de Java lo hacían).

No me he encontrado hasta ahora con algún servicio web RESTful que realmente envíe código del servidor al cliente y lo ejecute. Tal vez pueda ser utilizado para implementar lógica de cifrado en el cliente pero actualmente existen soluciones más eficientes para esto.

Métodos de HTTP

En REST la manera que tenemos para describir la acción que queremos realizar en un recurso es usando los métodos de HTTP.

GET

Lo usaremos para consultar colecciones de recursos o para consultar un recurso en concreto a través de su identificador único.

PUT

Lo usaremos para dos cosas:

Crear un recurso siempre y cuando el consumidor del API conozca el identificador de ese recurso (lo describiré con un ejemplo más adelante).

Actualizar un recurso de manera total y cuando me refiero a de manera total significa que en la petición deberé enviar todos y cada uno de los atributos de la entidad a actualizar.

PATCH

Lo utilizaremos para actualizar un recurso de manera parcial es decir hay ocasiones en que solo me interesará actualizar algunos de los atributos de una entidad por lo que en esos casos se recomienda el método PATCH en lugar del PUT.

POST

Lo usaremos para crear recursos y también para modelar operaciones que no se pueden modelar de manera natural con GET, PUT, PATCH y DELETE por ejemplo para realizar una validación por ejemplo si queremos modelar en REST una operación de validación se puede hacer así:

http://miservidor/mi-api/v0/films/54347/validate

HEAD

Funciona igual que el método GET pero tiene la diferencia de que HEAD no regresa nada en el cuerpo, en REST se suele utilizar para saber si un recurso existe o no.

DELETE

Se usa para eliminar un recurso de una manera física o incluso algunas veces se llega a usar para borrado lógico.

¿Cómo se suele representar una URI completa en APIs REST?

Una de las opciones usadas para dividir la URI de nuestra API es hacerlos de esta manera:

“protocolo/dominio/api/versión/recurso”

Esta es una manera recomendada para poder soportar diferentes versiones de nuestra API.

Ejemplos:

  • http:/mi_organizacion/api-films/v0/actors
  • http:/mi_organizacion/api-films/v0/films

Ejemplo: API de gestión de películas

Ahora que sabemos que es REST y sus características vamos a ejemplificar modelando para un API de gestión de Películas que permita:

  • Ver un listado de películas 
  • Ver información detallada de la película
  • Listar  los personajes que forman parte de la película
  • Poder ver el detalle de un personaje de la película
  • Crear una película

Modelo de Datos

El modelo de datos detrás de un API puede especificarse mediante cualquier lenguaje de modelado de datos conceptual como un modelo de entidad-relación simple o los clásicos diagramas de clase UML.

En este ejemplo asumimos el uso del modelo entidad-relación, el objetivo principal del modelo de datos detrás de un API es especificar las propiedades de los recursos manipulados por un API así como darnos una idea general de los nombres de los recursos a modelar.

Obtener el listado de películas

Por ejemplo si lo que queremos es modelar el recurso y método que represente el listado de películas lo podemos hacer de la siguiente manera (omitiendo protocolo y dominio en la URI):

GET /api-films/v0/films/

y lo que obtendríamos es una estructura JSON como esta:

Http 200 Ok

{
  "data": [
    {
      "filmId": "67490436a8be5153fe7d8bc54344fa1e",
      "name": "The Lord of the Rings: The Return of King",
      "duration": "3h 21m",
      "principalCast": [
        {
          "name": "Elijah",
          "lastName": "Wood",
          "actorId": "60490436a8be5153fe7d8bc54344fa78"
        },
        {
          "name": "Viggo",
          "lastName": "Mortensen",
          "actorId": "60390426a8be8153fe7d8bc54344fa7e"
        }
      ],
      "genres": [
        {
          "genreId": "Adventure",
          "description": "Adventure"
        },
        {
          "genreId": "Drama",
          "description": "Drama"
        },
        {
          "genreId": "Fantasy",
          "description": "Fantasy"
        }
      ],
      "images": [
        {
          "imageId": "60390426a8be8153fe7d8bc54344fa7e",
          "imageName": "small",
          "url": "images/igaj_2x_20.jpg",
          "size": "20 x 15"
        }
      ],
      "year": 2003,
      "rank": 8.9
    },
    {
      "filmId": "66490436a8be5153ae7d8bc54344fa17",
      "name": "21 Grams",
      "duration": "2h 4m",
      "principalCast": [
        {
          "name": "Sean",
          "lastName": "Penn",
          "actorId": "65490436b8be5153fe7y8bc54344fa78"
        },
        {
          "name": "Benicio",
          "lastName": "Del Toro",
          "actorId": "10390426a8be8153fe7d8bc54344fa7f"
        }
      ],
      "genres": [
        {
          "genreId": "Crime",
          "description": "Crime"
        },
        {
          "genreId": "Drama",
          "description": "Drama"
        },
        {
          "genreId": "Thriller",
          "description": "Thriller"
        }
      ],
      "images": [
        {
          "imageId": "80390426a8be8153fe7d8bc54344fa8e",
          "imageName": "small",
          "url": "images/igfrj_2x_20.jpg",
          "size": "20 x 15"
        }
      ],
      "year": 2003,
      "rank": 7.7
    }
  ]
}

Crear una película

Ahora bien si lo que deseamos es crear una nueva película lo más recomendable sería definirlo como un método POST. Si nos damos cuenta es la misma URI que para el listado de películas pero lo que cambia es el método:

POST /api-films/v0/films/

En este caso para crear la película tenemos que mandar como parte del payload de entrada los datos de la película:

{
  "name": "Star Wars: Episode IX - The Rise of Skywalker",
  "duration": "2h 22m",
  "principalCast": [
    {
      "name": "Daisy",
      "lastName": "Ridley",
      "actorId": "60490436a8be5153fe7d8bc54344fa78"
    },
    {
      "name": "John",
      "lastName": "Boyega",
      "actorId": "60390426a8be8153fe7d8bc54344fa7e"
    }
  ],
  "genres": [
    {
      "genreId": "Adventure",
      "description": "Adventure"
    },
    {
      "genreId": "Action",
      "description": "Action"
    },
    {
      "genreId": "Fantasy",
      "description": "Fantasy"
    }
  ],
  "images": [
    {
      "imageId": "60390426a8be8153fe7d8bc54344fa7e",
      "imageName": "small",
      "url": "images/igasw_2x_20.jpg",
      "size": "20 x 15"
    }
  ],
  "actors": [
    {
      "name": "Daisy",
      "lastName": "Ridley",
      "actorId": "60490436a8be5153fe7d8bc54344fa78"
    },
    {
      "name": "John",
      "lastName": "Boyega",
      "actorId": "60390426a8be8153fe7d8bc54344fa7e"
    },
    {
      "name": "Oscar",
      "lastName": "Isaac",
      "actorId": "60090426a8be8153fe7d8bc54344f45t"
    },
    {
      "name": "Mark",
      "lastName": "Hamill",
      "actorId": "20090426a8be8153fe0d8bc54344f7yy"
    }
  ],
  "directors": [
    {
      "directorId": "20000426a5be8153fe0d8bc54564f7yy",
      "secondLastName": "",
      "name": "J.J",
      "lastName": "Abrams"
    }
  ],
  "characters": [
    {
      "characterId": "20000426a5be8153fe0d8bc54564f7bb",
      "characterName": "Luke Skywalker",
      "actor": {
        "actorId": "20090426a8be8153fe0d8bc54344f7yy"
      }
    },
    {
      "characterId": "20000426a5be8153f66d8bc54564f766",
      "characterName": "Rey",
      "actor": {
        "actorId": "60490436a8be5153fe7d8bc54344fa78"
      }
    },
    {
      "characterId": "20000426a5be8153f66d8bc54564f444",
      "characterName": "Finn",
      "actor": {
        "actorId": "60090426a8be8153fe7d8bc54344f45t"
      }
    }
  ],
  "year": 2019,
  "rank": 6.7
}

Y lo que podemos recibir como respuesta es el objeto nuevo de película creado con su identificador generado en el servidor más todos los atributos del objeto:

Http 201 Created

{
  "data": {
    "filmId": "60590436a8be5153fe7d8bc54344faff",
    "name": "Star Wars: Episode IX - The Rise of Skywalker",
    "duration": "2h 22m",
    "principalCast": [
      {
        "name": "Daisy",
        "lastName": "Ridley",
        "actorId": "60490436a8be5153fe7d8bc54344fa78"
      },

     mas atributos ...

Obtener una película en particular

Los identificadores de las entidades se definen como Path params dentro de la URI por lo tanto si desearíamos obtener el detalle de una película lo haríamos de la siguiente manera:

GET /api-films/v0/films/67490436a8be5153fe7d8bc54344fa1e

y obtendríamos el detalle de una película:

Http 200 Ok

{
  "data": {
    "filmId": "67490436a8be5153fe7d8bc54344fa1e",
    "name": "The Lord of the Rings: The Return of King",
    "duration": "3h 21m",
    "principalCast": [
      {
        "name": "Elijah",
        "lastName": "Wood",
        "actorId": "60490436a8be5153fe7d8bc54344fa78"
      },
      {
        "name": "Viggo",
        "lastName": "Mortensen",
        "actorId": "60390426a8be8153fe7d8bc54344fa7e"
      }
    ],
    "genres": [
      {
        "genreId": "Adventure",
        "description": "Adventure"
      },
      {
        "genreId": "Drama",
        "description": "Drama"
      },
      {
        "genreId": "Fantasy",
        "description": "Fantasy"
      }
    ],
    "images": [
      {
        "imageId": "60390426a8be8153fe7d8bc54344fa7e",
        "imageName": "small",
        "url": "images/igaj_2x_20.jpg",
        "size": "20 x 15"
      }
    ],
    "actors": [
      {
        "name": "Elijah",
        "lastName": "Wood",
        "actorId": "60490436a8be5153fe7d8bc54344fa78"
      },
      {
        "name": "Viggo",
        "lastName": "Mortensen",
        "actorId": "60390426a8be8153fe7d8bc54344fa7e"
      },
      {
        "name": "Cate",
        "lastName": "Blanchett",
        "actorId": "60090426a8be8153fe7d8bc54344f45t"
      },
      {
        "name": "Orlando",
        "lastName": "Bloom",
        "actorId": "20090426a8be8153fe0d8bc54344f7yy"
      }
    ],
    "directors": [
      {
        "directorId": "20000426a5be8153fe0d8bc54564f7yy",
        "secondLastName": "",
        "name": "Peter",
        "lastName": "Jackson"
      }
    ],
    "characters": [
      {
        "characterId": "20000426a5be8153fe0d8bc54564f7bb",
        "characterName": "Legolas",
        "actor": {
          "actorId": "20090426a8be8153fe0d8bc54344f7yy"
        }
      },
      {
        "characterId": "20000426a5be8153f66d8bc54564f766",
        "characterName": "Frodo",
        "actor": {
          "actorId": "60490436a8be5153fe7d8bc54344fa78"
        }
      },
      {
        "characterId": "20000426a5be8153f66d8bc54564f444",
        "characterName": "Galadriel",
        "actor": {
          "actorId": "60090426a8be8153fe7d8bc54344f45t"
        }
      }
    ],
    "year": 2003,
    "rank": 8.9
  }
}

Obtener personajes de una película

Podemos enlazar entidades para que funcionalmente le demos más valor a nuestra API por ejemplo ¿Cómo modelaría el hecho de querer saber que personajes salen en una película dada?

GET /api-films/v0/films/123456/characters

¿Y si quisiéramos obtener la información de un solo personaje de la película? Lo haríamos de la siguiente forma:

GET /api-films/v0/films/123456/characters/20000426a5be8153fe0d8bc54564f7bb

Lo cual obtendríamos esta estructura JSON:

Http 200 Ok

{
  "data": {
    "characterId": "20000426a5be8153fe0d8bc54564f7bb",
    "characterName": "Legolas",
    "characterIntro": "He is a Sindarin Elf of the Woodland Realm and one of the nine members of the Fellowship who set off to destroy the One Ring.",
    "actor": {
      "actorId": "20090426a8be8153fe0d8bc54344f7yy"
    }
  }
}

Herramientas para modelar APIs REST

Existen herramientas que nos ayudan a describir y documentar nuestras APIs REST en primera instancia esto se hace mediante el uso de lenguajes estándar en la industria, en la actualidad son dos las herramientas líderes más utilizadas para este propósito: RAML y Swagger cada una con sus ventajas y desventajas, en este artículo no comentaré acerca de los detalles de cada lenguaje pero los invito a leer mi artículo que habla de RAML y sus diferencias respecto a Swagger.

Conclusiones

Al leer este articulo se tendrá una idea de qué es la arquitectura REST, en que se basa y cuáles son sus principales elementos.

REST tiene varias ventajas respecto a otros modelos más clásicos como XML-RPC o SOAP por lo que en la actualidad se ha vuelto muy popular dada su simplicidad y facilidad de consumo.

Dada su popularidad actual muchos desarrolladores se siente cómodos con REST ya que existe una amplia gama de librerías en diversos lenguajes de programación que lo soportan, incluso para la gente que no es técnica resulta entendible leer la documentación además de que se requiere mínima información para poder consumir este tipo de servicios.

Para finalizar recomiendo dos libros que a mi me sirvieron mucho para profundizar acerca del diseño de APIs Web con REST:

En mi siguiente artículo hablaré de como podemos expandir la funcionalidad de nuestra API de películas y hablaremos también de algunas buenas prácticas para aplicarlas a nuestras APIs REST.

Diseñando tus API’s REST con RAML

Hablando del diseño de API’s existe un principio que nos dice que todo software que se comunica con otro está acoplado de alguna manera, este acoplamiento puede ser débil o fuerte; El principio de Acoplamiento Débil del Servicio (Service Loose Coupling) descrito por Thomas Erl en su libro SOA Principles of Service Design nos dice que existen dos tipos de acoplamientos: positivos y acoplamientos negativos. Un tipo de acoplamiento positivo es aquel en el que el diseño del contrato del servicio se realiza antes de implementar la lógica interna al servicio ( aproach conocido como API First). Esto significa que yo como diseñador de API voy a poner atención principalmente en la definición del contrato sin voltear a ver en estos momentos al Backend para saber como lo vamos a implementar, esos detalles se describirán en etapas posteriores al diseño del contrato.

Entre otras ventajas, este enfoque también nos brinda el beneficio de permitirnos reemplazar la lógica del Backend de manera transparente y con un impacto prácticamente nulo de cara a aquellos que consumen el servicio, existen varios lenguajes en la industria que nos permiten definir el contrato de nuestra API REST, el lenguaje RAML es uno de ellos, nos permite enfocarnos en el diseño de nuestra API antes de implementarla.

¿Qué es RAML?

RAML es un lenguaje de modelado para APIs RESTful, el cual básicamente nos permite escribir el contrato del API y todos sus aspectos como definir sus recursos, métodos, parámetros, respuestas, tipos de medios y otros componentes HTTP básicos. Finalmente, puede usarse también para generar documentación más amigable de cara a los consumidores del API.

Pero RAML no es el único lenguaje usado en la industria para describir API’s RESTful, también existen otros productos como Open API Swagger ambos con mucha popularidad, sin embargo existen algunas diferencias entre ambos:

  • La gran característica para Swagger es que está diseñado como una especificación con aproach bottom-up (pone atención principalmente en la implementación y de ahí parte para exponer el contrato) , lo contrario a RAML que es una especificación del tipo top-down.
  • Swagger cuenta con una gran comunidad y algunas herramientas disponibles cosa que con RAML aún no es tanto, existen pocas herramientas para RAML por lo que algunas empresas que lo usan han optado por desarrollar sus propias herramientas customizadas a las necesidades propias.

Nomenclatura de RAML

RAML se basa en otro lenguaje llamado YAML el cual es un formato de datos legible por humanos (human friendly) que se alinea bien con los objetivos de diseño de la especificación RAML. Al igual que en YAML, todos los nodos son como claves, valores y etiquetas lo cual ayuda a comprender mejor la lectura de un fichero RAML.

RAML 1.0 (versión que se describe en este artículo) es la versión más actual del lenguaje, de tal manera que son documentos compatibles con YAML 1.2 que comienzan con una línea de comentarios YAML requerida que indica la versión RAML, de la siguiente manera:

#%RAML 1.0
title: My API

Types

RAML usa estructuras llamadas Types para especificar el modelo de entidades a utilizar dentro del API, es un modelo sencillo de representar a las entidades y a mi parecer más fácil de utilizar que otros modelos como por ejemplo JSON Schema.

Con la palabra reservada ‘Library’ en el encabezado especificamos que estamos creando una librería en este caso que contiene el type llamado user. Aunque podemos declarar varios types en una misma librería se suele crear una librería por cada Type.

#%RAML 1.0 Library

types:
  user:
    type: object
    properties:
      firstname: string
      lastname:  string
      age:       number

Un Type puede tener atributos sencillos como parte de su estructura o tener atributos más complejos (anidados) como en el siguiente ejemplo donde el atributo ‘status’ a su vez tiene mas atributos:

types:
  card:
    type: object
    properties:
      alias:
        type: string
        description: |
          Card alias. This attribute allows customers to assign a custom name to their cards.
        required: false
      status:
        type: object
        description: |
          Card current status.
        required: false
        properties:
          id:
            type: string
            enum: [INOPERATIVE, BLOCKED, PENDING_EMBOSSING, PENDING_DELIVERY, PRE_ACTIVATED]
           description: |
              Card status identifier.
          reason:
            type: string
            description: |
              Reason of the state of the card.
            required: false

Tipos de datos

RAML define diversos tipos de datos, parecidos a los que se definen en lenguajes de programación como Java:

  • object
  • array
  • union
  • tipos escalares: number, boolean, string, date-only, time-only, datetime-only, datetime, o integer

Los tipos se pueden clasificar respecto a los siguientes puntos:

  • Los tipos se clasifican dentro de 4 familias: external, object, array, y scalar.
  • Los ‘facets’ son configuraciones especiales, algunos de los facets permitidos son: properties, minPoperties, maxPoperties, additionalProperties, discriminator, y discriminatorValue.
  • Solo los objetos pueden declarar el ‘properties’ facelet.

Herencia

En RAML 1.0 se permite la Herencia, por ejemplo se puede tener un Type que represente a una Persona y aparte tener a un Type Teacher que herede algunas de sus atributos de Person:

types:
  person:
    type: object
    properties:
      name: string
  teacher:
    type: Person
properties:
      level: string

Resource Definitions

¿Cómo se modelan los recursos en RAML?

En el siguiente ejemplo vamos a modelar en REST un listado de películas y queremos definir un filtro opcional (queryParam) por el año en que salió la película, a continuación esperamos que el servidor nos conteste con un arreglo de películas que cumplen con ese criterio de búsqueda.

Técnicamente hablando definimos un recurso que se llamará /films el cual podríamos invocar de esta manera:

GET /films?year=

La declaración de ese endPoint en lenguaje RAML quedaría como:

/films:
  description: |
    Manage Films

  get:
    description: |
      Service for get films.

    queryParameters:
      year:
        description: Filters the film by release year.
        type: string
        example: “2009”
        required: false
    responses:
      200:
        body:
          application/json:
            type: object
            properties:
              data:
                type: films.films
                required: false

Métodos

Los métodos http cuelgan de cada recurso, subrecurso o URI Parameter como se ejemplifica a continuación:

/books:
    get:
    post:
    /{id}:
      get:
      put:
      delete:
      /chapters:
        get:

Responses

La definición de las respuestas deben venir acompañados del código http y del formato en el que vendrán los datos, generalmente las respuestas vienen en formato ‘application/json’. Se deben poner los códigos http de éxito el cual generalmente es uno 2XX así como los códigos de error, por ejemplo la invocación al endPoint puede regresar un error de tipo 404 ‘Recurso no encontrado’.

/books:
  …
  /{id}:
    get:
      description: Get a Book by id
      responses:
        200:
          body:
            application/json:
              type: book.book
404:
            description: Not found             

Ejemplo de API para gestión de películas

Para este blog a manera de ejemplo práctico vamos a modelar con RAML un API de películas que permita:

  • Ver un listado de películas 
    • Filtrar por año de estreno.
    • Ver información detallada de la película como la duración, el genero, el año de estreno o el ranking de calificación de los usuarios.
    • Listar  los directores que intervienen.
    • Listar los actores que intervienen.
    • Listar los personajes dentro de la película.
    • Listar las imágenes asociadas a un película (por ejemplo una imagen pequeña para mostrarse en un listado y una imagen grande para mostrase en un detalle).
    • Poder ver el detalle de un personaje de la película.
  • Dar de alta una nueva película.

De tal forma que se van a generar los siguientes endPoints:

Modelo de Datos

El modelo de datos detrás de un API puede especificarse mediante cualquier lenguaje de modelado de datos conceptual como por ejemplo un modelo de entidad-relación simple o con los clásicos diagramas de clase UML.

En este ejemplo utilizaremos un modelo entidad-relación para representar de manera gráfica la relación entre las entidades identificadas así como la definición de sus atributos.

La imagen tiene un atributo ALT vacío; su nombre de archivo es modelo_peliculas.png

Modelo de types de películas en RAML

En este caso lo primero que vamos a definir en RAML son los Types que representan al modelo de lo cuales obtendremos los siguientes archivos:

actor.raml
actors.raml
character.raml
characters.raml
director.raml
directors.raml
film.raml
films.raml
genre.raml
image.raml
person.raml
persons.raml

Cabe destacar que vamos a generar un Type que usaremos para representar el listado de un recurso y otro Type que represente el detalle del recurso (lo hacemos así porque para un listado solo vamos a mostrar algunos atributos y para el detalle otros), para el listado se nombrará al Type en plural. Por ejemplo tenemos un Type ‘film.raml’ que usaremos para mostrar los atributos de una película en un detalle y el Type ‘films.raml’ para mostrar los atributos de la película en el listado.

A continuación describiremos algunos de los types identificados (la lista completa se puede encontrar el el repositorio de GitHub anexo a este artículo).

person.raml

#%RAML 1.0 Library
uses:
 
types:
  person:
    type: object
    properties:
      name:
        type: string
        required: false
        description: |
          Person name.
      lastName:
        type: string
        required: false
        description: |
          Person last name.
      secondLastName:
        type: string
        required: false
        description: |
          Person second last name.
      birthDate:
        type: string
        required: false
        description: |
          Person birthdate.  

A continuación definiremos la entidad que representa a un actor el cual hereda algunas de sus propiedades de person.

Podemos hacer uso del nodo ‘uses’ para importar librerías externas, en este caso vamos a exportar la entidad ‘person’:

#%RAML 1.0 Library
uses:
  person: person.raml
 
types:
  actor:
    type: person.person
    properties:
      actorId:
        type: string
        required: false
        description: |
          Unique actor identifier.
      actorIntro:
        type: string
        required: false
        description: |
          Actor intro text.

A continuación la definición del Type que representa al genero de la película, aquí podemos ver que hacemos uso de la estructura ‘enum’ el cual nos permite delimitar los posibles valores del genero:

#%RAML 1.0 Library
uses:
  
types:
  genre:
    type: object    
    properties:
      genreId:
        type: string
        required: false
        description: |
          Unique director identifier.   
        enum: [“Sci-Fi”,”Action”,”Adventure”,”Fantasy”,”Thriller”,”Crime”, “Drama”]
      description:
        type: string
        required: false
        description: |
          Genre description. 

Y a continuación la representación de un listado de películas:

#%RAML 1.0 Library
uses:
  character: character.raml
  director: director.raml
  genre: genre.raml
  image: image.raml

types:
  films:
    type: array
    items:
      properties:
        filmId:
          type: string
          required: false
          description: |
            Unique film identifier.
        name:
          type: string
          required: false
          description: |
            Film name.
        duration:
          type: string
          required: false
          description: |
            duration in minutes.
        principalCast: 
          type: character.character[]
          required: false
          description: |
            film principal cast.  
        genres:
          type: genre.genre[]
          required: false
          description: |
            film genres.
        images:
          type: image.image[]
          required: false
          description: |
            film genres.
        year:
          type: number
          required: false
          description: |
            film release year.
        rank:
          type: number
          required: false
          description: |
            film users rank.

Definir los recursos del API de películas

Para hacer la definición de los recursos de nuestra API la haremos en un archivo llamado ‘api.raml’ donde estarán definidos todos los recursos del API. Para comenzar describiremos la sección de los encabezados y la importación de los types.

#%RAML 1.0
title: FILMS
version: v0.1.0
baseUri: https://www.mysite.com/filmsapi/v0

uses:
  actors: types/actors.raml
  directors: types/directors.raml
  characters: types/characters.raml
  character: types/character.raml
  films: types/films.raml
  film: types/film.raml
  image: types/image.raml

Listado de Películas

Ahora vamos a modelar la siguiente funcionalidad con RAML:

  • Vamos a definir un recurso REST llamado ‘/films’ que gestione las operaciones a realizar sobre las películas.
  • Vamos a definir un método GET para poder obtener un listado de películas.
  • Vamos a definir un ‘queryParameter’ llamado ‘year’ con el cual vamos a poder listar las películas filtradas con una fecha de lanzamiento dada.
/films:
  description: |
    Manage Films

  get:
    description: |
      Service for get films.

    queryParameters:
      year:
        description: Filters the film by release year.
        type: string
        example: “2009”
        required: false
    responses:
      200:
        body:
          application/json:
            type: object
            properties:
              data:
                type: films.films
                required: false
      204:
        description: Not Content
      400:
        description: Bad Request
      401:
        description: Unauthorized
      403:
        description: Forbidden
      500:
        description: Server Error      

Las partes importantes de la definición del bloque anterior es que en la respuesta vamos a modelar que se espera que el servicio regrese la información en notación JSON dentro de un nodo llamado data el cual es de tipo array y cada elemento del array regresará la información de cada película como se ve en el siguiente ejemplo JSON:

“data”: [
    {
      “filmId”: “67490436a8be5153fe7d8bc54344fa1e”,
      “name”: “The Lord of the Rings: The Return of King”,
      “duration”: “3h 21m”,
      “principalCast”: [
        {
          “name”: “Elijah”,
          “lastName”: “Wood”,
          “actorId”: “60490436a8be5153fe7d8bc54344fa78”
        },
        {
          “name”: “Viggo”,
          “lastName”: “Mortensen”,
          “actorId”: “60390426a8be8153fe7d8bc54344fa7e”
        }
      ],
      “genres”: [
        {
          “genreId”: “1”,
          “description”: “Adventure”
        },
        {
          “genreId”: “2”,
          “description”: “Drama”
        },
        {
          “genreId”: “3”,
          “description”: “Fantasy”
        }
      ],
      “images”: [
        {
          “imageId”: “60390426a8be8153fe7d8bc54344fa7e”,
          “imageName”: “small”,
          “url”: “images/igaj_2x_20.jpg”,
          “size”: “20 x 15”
        }
      ],
      “year”: 2003,
      “rank”: 8.9
    },
    {
      “filmId”: “66490436a8be5153ae7d8bc54344fa17”,
      “name”: “21 Grams”,
      “duration”: “2h 4m”,
      “principalCast”: [
        {
          “name”: “Sean”,
          “lastName”: “Penn”,
          “actorId”: “65490436b8be5153fe7y8bc54344fa78”
        },
        {
          “name”: “Benicio”,
          “lastName”: “Del Toro”,
          “actorId”: “10390426a8be8153fe7d8bc54344fa7f”
        }
      ],
      “genres”: [
        {
          “genreId”: “1”,
          “description”: “Crime”
        },
        {
          “genreId”: “2”,
          “description”: “Drama”
        },
        {
          “genreId”: “3”,
          “description”: “Thriller”
        }
      ],
      “images”: [
        {
          “imageId”: “80390426a8be8153fe7d8bc54344fa8e”,
          “imageName”: “small”,
          “url”: “images/igfrj_2x_20.jpg”,
          “size”: “20 x 15”
        }
      ],
      “year”: 2003,
      “rank”: 7.7
    }
  ]
}

Definiendo ejemplos JSON en el API

Se pueden definir ejemplos JSON para ayudarle al consumidor del API a darse una idea de la información que regresará el endPoint como se ve a continuación. El archivo llamado ‘get_200.json’ contiene el ejemplo de los datos regresados por el endPoint:

/films:
  description: |
    Manage Films

  get:
    description: |
      Service for get films.

    queryParameters:
      year:
        description: Filters the film by release year.
        type: string
        example: “2009”
        required: false
    responses:
      200:
        body:
          application/json:
            example: !include examples/films/get_200.json
            type: object
            properties:
              data:
                type: films.films
                required: false

Obtener el detalle de una película

Ahora vamos a definir el URI parameter para poder obtener el detalle de una película, las llaves {} alrededor del nombre del parámetro son los que nos indican la declaración del URI parameter. La declaración se hace de esta manera:

GET /films/67490436a8be5153fe7d8bc54344fa1e

/{film-id}:
    description: |
      Manages the information of a specific Film.
    uriParameters:
      film-id:
        description: “Unique film identifier”
        type: string
        example: “1234”

    get:
      description: |
          Service for retrieving the information associated to a specific film.

      responses:
        200:
          description: Ok
          body:
            application/json:
example: !include examples/films/film-id/get_200.json
              type: object
              properties:
                data:
                  type: film.film       
        401:
          description: Unauthorized
        403:
          description: Forbidden
        404:
          description: Not found
        500:
          description: Server Error

Poder ver el detalle de un personaje dentro de la película

A continuación veremos el fragmento donde se declara la sección para poder ver el detalle de un personaje de la película:

GET /films/67490436a8be5153fe7d8bc54344fa1e/characters/00490436a8be5153fe7d8bc54344fa4

/{character-id}:
        description: |
          Manages the information of a specific Character.
        uriParameters:
          film-id:
            description: “Unique character identifier”
            type: string
            example: “1234”
        get:
          description: |
              Service for retrieving the information associated to a specific character.

          responses:
            200:
              description: Ok
              body:
                application/json:
                  example: !include examples/films/film-id/characters/character-id/get_200.json
                  type: object
                  properties:
                    data:
                      type: character.character       
            401:
              description: Unauthorized
            403:
              description: Forbidden
            404:
              description: Not found
            500:
              description: Server Error     

Crear una película nueva

El siguiente fragmento muestra como crear una nueva película:

POST /films/

post:
    description: |
      Service to create a new Film.

    body:
      application/json:
        example: !include examples/films/post_request.json
        type: film.film
    responses:
      201:
        description: Created
        headers:
          Location:
            displayName: location
            description: This header must provide the URI to reach the created resource.
            type: string
            required: true
            example: “/films/1”
        body:
          application/json:
            example: !include examples/films/post_201.json
            type: object
            properties:
              data:
                type: film.film
      400:
        body:
          application/json:
            type: object
            properties:
              message:
                type: error.error
                required: false
        description: Bad Request
      401:
        description: Unauthorized
      403:
        description: Forbidden
      500:
        description: Server Error 

En el ejemplo mostrado en el fragmento anterior (a diferencia de los métodos GET) para métodos POST se tiene que enviar un payload de entrada además de que también el endPoint regresará un encabezado http llamado ‘Location’ con la ubicación del recurso recién creado.

Conclusión

Este artículo habla de la introducción al lenguaje para modelar API’s REST llamado RAML (RESTful API Modeling Language), se describieron algunas sintaxis básicas del lenguaje, además se demostró su uso con un ejemplo práctico para un API de gestión de películas. El código completo de este ejemplo se puede descargar desde mi GitHub el cual está disponible aquí.

En una próxima entrega describiré sintaxis más complejas del lenguaje RAML como es el uso de traits, resource-types y custom-anotations, features que nos van a permitir escribir código RAML de manera más eficiente y reutilizable. También en futuras entregas hablaré de una herramienta que nos permitirá diseñar nuestras API’s RAML de manera gráfica. Por el momento les dejo estas dos referencias de herramientas para editar API’s RESTful usando RAML:

Para escribir el código de este ejemplo lo he hecho usando Sublime usando el Plugin para RAML y me ha resultado bastante útil, más adelante veremos que existen herramientas más amigables para la edición de nuestro código de momento nos va bien hacerlo en este editor.

También dejo el link con referencia a la especificación del lenguaje en su versión 1.0 la más actual.

Spring Data: Haciendo fácil la persistencia

Persistencia en Java

Históricamente en Java se han implementado  diversos frameworks y librerías para interactuar con la capa de pesistencia, tipicamentente con una base de datos relacional y un poco más reciente con bases de datos no relacionales. Algunos de estos ejemplos son:

  • JDBC
  • EJB 2.0
  • MyBatis
  • JDBC Template
  • JDO
  • Hibernate
  • JPA
  • Spring Data

Spring  Data

Si bien con el paso de los años diversos frameworks han ido madurando, algunos de ellos en su momento fueron muy exitosos hoy por hoy Spring Data es uno de los frameworks que más ventajas nos ofrecen sobre otras opciones, a continuación se destacan algunas de sus características:

  • Es un proyecto de Spring Framework que contiene subproyectos por cada base de datos distinta por ejemplo para MongoDB, MySQL, etc.
  • Nos ofrece una capa de abstracción a través de componentes de software llamados “repositorios” la cual es una solución al patrón de diseño ‘Generic DAO’ para implementar el CRUD.
  • Spring Data es una capa encima de JPA, así que ambas tecnologías se complementan como veremos más adelante.
  • No es necesario escribir clases de implementación, el framework escribe esas clases por nosotros.
  • Trabaja con fuentes de datos relacionales y no relacionales.

Ejercicio de demostración

Para este ejercicio vamos a hacer un servicio REST que obtenga una lista de empleados, dicha lista la vamos a poder filtrar por diversos criterios además de que también vamos a regresar la información paginada, todo con ayuda de Spring Data.

Los datos los almacenaremos en una base de datos de tipo NoSQL en este caso MongoDB, además para hacernos la vida mas sencilla vamos a utilizar Spring Boot que nos ayudará a hacer las configuraciones base del proyecto o ‘boilerplate’ como se le conoce en inglés.

El ejemplo consiste en los siguientes pasos:

  • Configurar y correr proyecto base (Spring Boot).
  • Configurar MongoDB.
  • Implementar las operaciones CRUD de una entidad sinple usando ‘CrudRepository’.
  • Utilizar ‘Automatic Custom Queries’ para filtrar resultados de búsqueda.
  • Utilizar ‘QueryByExample’ para filtrar por varios campos en una búsqueda.
  • Paginar los resultados de una búsqueda.

Prerequisitos

Configurar MongoDB

1 Levantar Mongo

2 Crear la base de datos

3 Crear una colección

En este caso yo le llamé a mi colección ‘enployees’.

Estructura del proyecto

Pueden descargar de mi repositorio de github el código base del proyecto.

La estructura del proyecto esta separada en diversos paquetes para tener mejor organización de los componentes.

Configurar Mongo desde la App

1 Modificar el pom.xml

Necesitamos agregar la dependencia de mongodb para Spring Data:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>

2 Conexión a la Base de Datos

Vamos a modificar el archivo application.properties para hacer las conexiones a la base de datos.

#mongodb
spring.data.mongodb.host=localhostspring.data.mongodb.port=27017
spring.data.mongodb.database=tvshows
#logging
logging.level.org.springframework.data=debuglogging.level.=info

Obtener los datos de la colección desde Mongo

1 Modificar POJO

Necesitamos ahora realizar las anotaciones de un objeto Java que nos represente la colección a dónde se almacenarán los datos en MongoDB, en este caso información básica de un empleado y adicionalmente una lista de habilidades por cada empleado:

package com.example.microservicedemo.model;

import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;


@Document(collection = “employees”)
public class Employee {

@Id
private String id;

@Field()
private String name;

@Field()
private Integer age;

@Field()
private List<Skill> skills;

2. Crear repository de Spring Data

Crear en el paquete com\example\microservicedemo\dao\repository la clase ‘EmployeesRepository’.

Aquí es donde realmente ponemos a nuestro beneficio la magia de Spring Data ya que solamente tenemos que crear una interfaz que herede de la clase ‘CrudRepository’ de Spring Data’ en este caso el framework implementará por nosotros la funcionalidad para hacer CRUD de nuestra colección de empleados. Con el soporte de Spring Data, la tarea repetitiva de crear las implementaciones concretas del patrón DAO se simplifica porque solo vamos a necesitar definir la interfaz y no más.

La interfaz CrudRepository nos va a proporcionar métodos genéricos cómo save, findAll, findById, delete, count, etc.

package com.example.microservicedemo.dao.repository;

import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.example.microservicedemo.model.Employee;

@Repository
public interface EmployeesRepository extends CrudRepository<Employee, String>{ }

3. Implementar lógica en el servicio

Dentro de la lógica del servicio se inyectará la dependencia al repositorio de Spring Data y utilizar algunos de sus métodos como findOne, findAll, findById, y save.

package com.example.microservicedemo.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import com.example.microservicedemo.dao.repository.EmployeesRepository;
import com.example.microservicedemo.model.Employee;
import com.example.microservicedemo.service.IEmployeesService;

public class EmployeesServiceImpl implements IEmployeesService<Employee, String> {

@Autowired
EmployeesRepository employeeRepository;

@Override
public Employee findById(String id) {
return employeeRepository.findOne(id);
}

@Override
public Iterable<Employee> findAll(Employee employeeExample){
Iterable<Employee> employees; employees = employeeRepository.findAll();
return employees;
}

public Employee save(Employee employee) {
return employeeRepository.save(employee);
}

@Override
public void delete(String id) {
//TODO
}
}

4. Insertar algunos registros desde Postman:

A continuación vamos a insertar algunos registros en la colección de empleados:  

POST localhost:8080/api/employees/

Mensaje POST

{    
“name”: “Snoopy”,    
“age”: 33,    
“skills”: [       
 {  “name”: “Java”,            “experience”: 3        },
  {  “name”: “RAML”,            “experience”: 1 }
]
}

5. Levantar la app e invocar a endPoints

Vamos a realizar una prueba para invocar a dos endPoints REST en este caso para obtener una lista de empleados y la segunda para obtener un empleado en concreto:

 GET  localhost:8080/api/employees/

 GET  localhost:8080/api/employees/<id>

Filtrar los datos mediante un campo de entrada

Ahora lo que haremos es modificar nuestra clase de repositorio para agregar funcionalidad de filtrado. Los repositorios de Spring Data soportan agregar métodos personalizados que por medio del nombre del método se le diga al framework que se quiere hacer un query por algún criterio de búsqueda, en este caso queremos filtrar a los empleados por su edad así que declaramos un método findEmployeesByAge siguiendo una convención de nomenclatura podremos realizar el siguiente tipo de consultas:

1.Modificar el repositorio para usar un método de consulta

package com.example.microservicedemo.dao.repository;

import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.example.microservicedemo.model.Employee;

@Repository
public interface EmployeesRepository extends CrudRepository<Employee, String>{
List<Employee> findEmployeeByAge(int age);
}

2. Modificar el servicio para filtrar

En este caso vamos a a hacer uso del parámetro de entrada ‘employeeExample’ el cual utilizaremos para saber si el consumidor del endPoint desea filtrar por edad y así llamar a nuestro método personalizado ‘findEmployeeByAge’. En caso contrario de que no se filtre por edad entonces se obtendrán todos los empleados de la colección.

package com.example.microservicedemo.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import com.example.microservicedemo.dao.repository.EmployeesRepository;
import com.example.microservicedemo.model.Employee;
import com.example.microservicedemo.service.IEmployeesService;

public class EmployeesServiceImpl implements IEmployeesService<Employee, String> {

@Autowired
EmployeesRepository employeeRepository;

@Override
public Employee findById(String id) {
return employeeRepository.findOne(id);
}

@Override
public Iterable<Employee> findAll(Employee employeeExample){
Iterable<Employee> employees;

if(employeeExample != null && employeeExample.getAge() != null  &&  employeeExample.getAge() > 0){
employees = employeeRepository.findEmployeeByAge(employeeExample.getAge());
} else {
employees = employeeRepository.findAll();
}
return employees;

}

public Employee save(Employee employee) {
return employeeRepository.save(employee);
}

@Override
public void delete(String id) { //TODO }

}

3. Modificar el controller para recibir el parámetro

Ahora vamos a realizar la modificación al controller ‘EmployeesController’ ya que vamos a agregar el filtro por edad, para esto nos vamos a ayudar de la clase ‘GenericMapper’ la cual se encargará de crear una instancia de la clase ‘Employee’ que tiene seteado un valor para el atributo ‘age’ de tal manera que el método ‘findAll’ del servicio identifique el filtro.

@GET
@Produces(MediaType.APPLICATION_JSON)
public Response findAll(@QueryParam(“age”) final int age) {
int statusCode = 200;
Response response = null;
List<KeyValue> parameters = new ArrayList<KeyValue>();
if(age > 0) parameters.add(new KeyValue(“age”, age));
Employee dto = (Employee) GenericMapper.mapping(new Employee(), parameters);
Iterable<Employee> employees = employeesService.findAll(dto);

response = Response.status(statusCode).entity(employees).build();
return response;
}

4. Probar

Probaremos el caso en el que el consumidor del endPoint desea filtrar por edad del empleado:

1 Levantar la app

2 invocar GET  localhost:8080/api/employees/?age=22

Filtrar los datos mediante más de un campo de entrada

Es común el desear filtrar por más de uno de los atributos de la entidad ‘Employee’ en este caso vamos a hacer la modificación para filtrar por edad y nombre aplicado las combinaciones posibles:

consulta sin filtro, consulta por edad, consulta por nombre, consulta por nombre y edad.

1 Modificar el controller para filtrar por más de un campo de entrada

Realizaremos la modificación al controller de la siguiente manera para poder soportar criterios de búsqueda múltiples:

@GET
@Produces(MediaType.APPLICATION_JSON)
public Response findAll(@QueryParam(“age”) final int age, @QueryParam(“name”) final String name) {
int statusCode = 200;
Response response = null;
List parameters = new ArrayList();
if(age > 0) parameters.add(new KeyValue(“age”, age));
if(name != null) parameters.add(new KeyValue(“name”, name));
Employee dto = (Employee) GenericMapper.mapping(new Employee(), parameters);
Iterable employees = employeesService.findAll(dto);
response = Response.status(statusCode).entity(employees).build();
return response;
}

2. Modificar el servicio

Del lado del servicio ‘EmployeesServiceImpl’ vamos a realizar una modificación, usaremos la clase ‘Example’ de Spring Data la cual nos ayudará a poder hacer un query que contenga los criterios deseados:

@Override
public Iterable findAll(Employee employeeExample){
System.out.println(employeeExample);
Iterable employees;
employees = employeeRepository.findAll(Example.of(employeeExample));
return employees;
}

Con esos pequeños cambios ahora el endPoint va a soportar el obtener datos filtrados por dos campos.

3 Cambiar a MongoRepository

Para poder hacer cosas más avanzadas Spring Data nos da la posibilidad de extender su funcionamiento en este caso vamos a hacer uso de otra clase que se llama ‘MongoRepository’ la cual nos va a permitir hacer consultas mas complejas, en este caso poder filtrar de manera dinámica por diversos campos sin necesidad de estar programando queries por cada tipo de combinación posible:

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.example.microservicedemo.model.Employee;
@Repository
public interface EmployeesRepository extends MongoRepository{
List findEmployeeByAge(int age);
}

4 Probar

1 Levantar la app

2 invocar GET  localhost:8080/api/employees/

3 invocar GET  localhost:8080/api/employees/?age=22

4 invocar GET  localhost:8080/api/employees/?name=Jorge

5 invocar GET  localhost:8080/api/employees/?age=22 &name=Jorge

Regresar los datos paginados

Finalmente como parte de nuestro ejercicio vamos a regresar los datos de manera paginada.

1. Modificar la interfaz del servicio

Agregaremos un parámetro de entrada con el numero de pagina deseado:

package com.example.microservicedemo.service;

import com.example.microservicedemo.model.Employee;


public interface IEmployeesService<T, ID> {

public T findById(ID id);

public Iterable<T> findAll(T exampleEmployee, int page);

public T save(Employee employee);

public void delete(ID id);

}

2. Modificar la implementación del servicio para que pagine de 5 en 5 registros

package com.example.microservicedemo.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Page;


import com.example.microservicedemo.dao.repository.EmployeesRepository;
import com.example.microservicedemo.model.Employee;
import com.example.microservicedemo.service.IEmployeesService;

public class EmployeesServiceImpl implements IEmployeesService<Employee, String> {

@Autowired
EmployeesRepository employeeRepository;

@Override
public Employee findById(String id) {
return employeeRepository.findOne(id);
}

@Override
public Iterable<Employee> findAll(Employee exampleEmployee, int page){
Iterable<Employee> employees;

if(page > 0){
Pageable pageable = new PageRequest(page-1, 5);
employees = employeeRepository.findAll(Example.of(exampleEmployee), pageable);
} else {
employees = employeeRepository.findAll(Example.of(exampleEmployee));
}


return employees;
}

public Employee save(Employee employee) {
return employeeRepository.save(employee);
}

@Override
public void delete(String id) {
//TODO
}

}

3. Modificar el controller para implementar paginación

@GET
@Produces(MediaType.APPLICATION_JSON)
public Response findAll(@QueryParam(“age”) final int age, @QueryParam(“name”) final String name, @QueryParam(“page”) final int page) {
int statusCode = 200;
Response response = null;

List<KeyValue> parameters = new ArrayList<KeyValue>();
if(age > 0 ) parameters.add(new KeyValue(“age”, age));
parameters.add(new KeyValue(“name”, name));

Employee dto = (Employee) GenericMapper.mapping(new Employee(), parameters);

Iterable<Employee> employees = null;
employees = employeesService.findAll(dto, page);
response = Response.status(statusCode).entity(employees).build();

return response;
}

4. Levantar la app e invocar diversos escenarios

En caso de que no se proporcione el ‘query param’ ‘page’ eso significaría que se desea obtener la primer página.

GET  localhost:8080/api/employees/

GET  localhost:8080/api/employees/?page=1

GET  localhost:8080/api/employees/?page=2

GET  localhost:8080/api/employees/?age=22&page=1

Conclusiones

Hemos visto las ventajas de utilizar Spring Data, lo que podemos destacar de este ejercicio es que con poco esfuerzo pudimos implementar servicios REST con interacción a base de datos.

El framework de Spring Data nos ayudó a implementar funcionalidad por nosotros ya que por nuestra parte solo declaramos interfaces para utilizar los repositorios.

También aprendimos a utilizar algunas clases que Spring Data nos ofrece para hacer filtrados , queries dinámicos y aprendimos a implementar funcionalidad de paginado.

Sin duda Spring Data nos ayuda a interactuar con bases de datos de una manera sencilla sin demasiado esfuerzo de nuestra parte para implementar patrón DAO.