Construyendo un Web Api II: Versionado

«Tus clientes más descontentos son tu mayor fuente de aprendizaje»

–Bill Gates

Proyectos anteriores:

Para mí, el siguiente paso a dar al crear un API REST es versionarlo. El día de mañana no vamos a poder romper la estructura de nuestra API de golpe ya que nuestros clientes sufrirían un cambio catastrófico para ellos. Puede que existan algunos de los que incluso no se quieran actualizar y se mantengan en la versión anterior. Por ello, esta vez vamos a versionar nuestra API.

El proceso de versionado es muy sencillo. Veremos que no cuesta nada de trabajo actualizarlo.

En este proyecto vamos a:

  • Sobreescribir el atributo Route para incorporarle versionado.
  • Crear un TestRunner para probar nuestros progresos. El TestRunner lo usaremos para probar las diferentes características, mientras que el proyecto principal ubicado en src lo dejaremos para poder cogerlo y reutilizarlo el día de mañana.
  • Depurar con Postman el resultado esperado.

Versionado en API REST

Existen varias maneras de versionar un Api:

Versionado en la URI

Explicitar el versionado en la ruta. Por ejemplo:

 

Desde mi punto de vista este versionado es demasiado invasivo en la url del Api. Al fin y al cabo, la url es para indicar dónde se encuentra un recurso. Una versión no tiene nada que ver con el recurso, por lo que conceptualmente no sería del todo correcto poner la versión aquí.
Semánticamente no es muy correcto; pero eso sí es muy fácil de usar.

Parámetro en el Query String

Pasar el parámetro de la versión por el query string:

Habría que mirar cada vez el parámetro que indica la versión. De nuevo el problema principal es semántico, los parámetros deberían ser para pedir un recurso, no para poder pedir una versión.

Cabecera Accept

La cabecera Accept es la que usa el cliente para pedir el tipo de los datos en que quieren que le lleguen. Normalmente un ejemplo de esta cabecera es:

En este caso el formato que se pide es el formato json.En esta cabecera podríamos pedir también la versión en la que queremos que nos vengan los datos en una especificación creada por nosotros:

vnd indica que es una definición propia y con ello podemos en nuestra API leer el dato y darle la versión correspondiente.

Esta solución está cogida por pinzas, ya que cada cabecera debería tener un propósito y no mezclarlas.

Cabecera personalizada

El protocolo HTTP permite poder crear cabeceras en las llamadas personalizadas, por lo que podemos crear nuestra propia cabecera:

Esta es la solución que a mí más me gusta y que implementaremos. Así, no mezclamos las cabeceras de Accept-Header ni invadimos los parámetros ni las urls.

ASP.NET Web Api: Atributo Route

Como vimos en el proyecto anterior, ASP.NET Web Api nos permite poder poner la ruta en cada uno de los métodos de nuestros Controladores:

El problema del atributo Route es que no es suficiente. Si nuestra API evoluciona a una versión posterior después que nuestros clientes ya están en producción con una primera versión y los métodos cambian en nomenclatura, esto les afectaría en su funcionamiento. Por ello, debemos añadirle a cada una de estas funciones un nuevo parámetro que se corresponda con el número de la versión a la que pertenecen. Por tanto, nuestros endpoints estarán formados por la dirección y su versión.

He creado en src una carpeta Api.Features y he incluido en ella una carpeta Versioned que contiene un proyecto llamado Api.Versioned sobre el que vamos a trabajar.

También he añadido una carpeta Api.Helpers para ir incluyendo Helpers que podríamos utilizar en cualquier Feature que vayamos implementando en el futuro.

La primera clase que vamos a crear es VersionRoutedAttribute, que va a implementar nuestra propia versión del Atributo Route:

El constructor del atributo recibirá dos parámetros:

  • Uno con la dirección de la llamada, que le pasaremos a nuestra clase base RoutedAttribute para que se encargue de ella.
  • Otro con la versión de esa llamada.

En el diccionario de restricciones vamos a añadir una restricción personalizada que indique cuál debe ser la versión correcta.

En el proyecto anterior vimos como las rutas podían especificarse poniendo valores por defecto y restricciones:

En el caso de DefaultApi la restricción es que el id sea un entero. Nosotros podemos crear nuestras propias restricciones cumpliendo el interfaz IHttpRouteConstraint. La clase VersionConstraint tendrá ese objetivo:

La clave está en el método Match:

  • Primero comprobamos si la dirección es la misma que tenemos establecida, si no es así devolvemos false ya que no es el método correcto.
  • Si coinciden en la dirección entonces tendremos que comprobar que versión viene en la llamada. Este sería el punto dónde tenemos que decidir lo visto en la parte del versionado de las APIs. Del parámetro Request podríamos sacar cualquier tipo de información de la llamada. A mí me gusta poner una cabecera personalizada ya que deja la llamada más limpia. El método privado GetVersion va a buscar una cabecera cuyo nombre sea la que hemos definido en las constantes VersionConstants.
  • Si hay cabecera el valor se comparará con el valor del atributo que se les ha pasado. Si no hay cabecera se comparará con la versión por defecto de la API, es decir, si la API tiene un valor por defecto de 3 y queremos llamar a la versión 3 del API no hará falta que le pasemos ninguna cabecera con este 3. Esto normalmente se hace para no actualizar la versión 1 de las APIs y que se tengan que añadir cabeceras en versiones posteriores, para no tocar los clientes que ya usan la versión 1.

En la clase estática VersionConstants tenemos los valores por defecto de la descripción de la cabecera (VersionHeader «api-header») y de la versión de la API por defecto (VersionDefault 1) si no viene ninguna cabecera:

El Helper ConfigurationManagerHelper usa los otros dos valores de VersionConstants para que se puedan sobrescribir desde el WebConfig. Si en el WebConfig hay un registro clave-valor sobrescribiendo el api-description o el api-version-default se cogerá ese valor en lugar del de VersionConstants. En caso contrario se usarán los valores por defectos definidos en dicha clase.

Esta clase puede ser reutilizada en alguna otra Feature que coja valores del WebConfig. Por eso la he separado de la Feature de versionado. La posible reutilización siempre es importante, habla con tus objetos.

Por último, actualizamos nuestro controlador para dejarlo con el nuevo atributo:

TestRunner Versionado

Para poder testear la nueva funcionalidad vamos a crear una carpeta test/TestRunner con una copia de nuestra API. Dentro vamos a poner un controlador con el nuevo atributo Versioned para poder probarlo:

En el webconfig de la aplicación he añadido una línea para poner la versión 2 por defecto:

Para probar el Api voy a usar Postman. Postman es una herramienta creada por Google que podéis descargar aquí: https://www.getpostman.com/


Como ellos dicen en su propia Web: «Desarrolar APIs es difícil, Postman hace que sea más fácil». Para testear nuestros resultados es una herramienta muy potente ya que nos permite manejar las llamadas de una API y meter cabeceras en la misma de manera muy sencilla.

Vamos a configurar la primera llamada al Api:



La primera llamada sin cabeceras recibe por defecto la versión 2, como está indicado en el webconfig. En cambio si añadimos la cabecera de que la versión que queremos es la 1:

Poner la cabecera con la versión 2 del Api o sin ella es equivalente:

Lo lógico es que la versión por defecto sea la 1 ya que en la primera versión puede no ser necesario añadir la cabecera. En cambio, al pasar a una versión posterior los clientes se tendrían que actualizar. Para no romper sus llamadas, lo más lógico sería añadir esa cabecera a partir de la segunda versión, evolucionando así nuestra API.

Si hacemos la llamadas con la cabecera con valor versión 3, el NotFoundController visto en el proyecto anterior entra en escena y nos devuelve un error 404, ya que para esa versión la url no existe:

Conclusiones

Versionar el Api es muy sencillo y nos puede ahorrar muchos quebraderos de cabeza en el futuro. Por ello, mejor ponerlo desde el principio para no causar problemas a las aplicaciones clientes que consuman nuestra Api en el futuro.

Postman es una muy buena herramienta para depurar nuestras Apis y además tiene un interfaz muy fácil de usar y muy amigable.

Referencias