rest versioning architecture with asp.net mvc web api v1.2
TRANSCRIPT
REST API Versioning Techniques
Arquitecto de Soluciones de SoftwareRodrigo Liberoff
with ASP.NET Web API
“Si al principio no tienes éxito, llámalo versión 1.0.”― Anónimo -
Desconocido
Introducción
Youtube
Wikipedia
TwitterLinkedI
n
Wordpress
YahooEl Mundo
El desarrollo de Web APIs está explotando…
…y las compañías están exigiéndolo.
• Crear un Web API es muy fácil.
• Hacerlo bien también es fácil, pero no tan sencillo.
• Pensar en su evolución, bastante más complicado.
• Los problemas aparecen tras desplegar la primera
versión.
• Es muy común romper la compatibilidad con los
clientes.
Los creadores de HTTP pensaron mucho sobre esto y sobre cómo diseñar con miras a la evolución.
El cambio es inevitable.
Tenemos que pensar y diseñar nuestros Web API para que se puedan adaptar al cambio con el paso del tiempo.
El software evoluciona, y por tanto sus APIs evolucionan.
Asumir, que el Web API que construyamos hoy… va a evolucionar.
La pregunta no es “si evoluciona”...…la pregunta es “cómo evolucionará”.
• ¿Cómo afecta a los consumidores agregar nuevas características al Web API?
• ¿Los obliga a actualizarse y re-desplegar su producto?
• ¿Pueden continuar operando?
• ¿Cómo está securizado nuestro Web API?
• ¿Será capaz de adaptarse a nuevos estándares y protocolos de seguridad?
• ¿Podrá soportar nuevas tendencias en clientes y dispositivos?
Conceptos
Un URIEs un identificador de recursos uniforme – del inglés uniform resource identifier.
Es una cadena de caracteres que identifica los recursos de una red de forma unívoca.
Se diferencia de un localizador de recursos uniforme (URL) en que estos últimos hacen referencia a recursos que pueden variar en el tiempo.
¿QUÉ
ES…
?
Un Recurso
Es cualquier cosa que tiene un URI asignado.
No está restringido a ser un archivo. Un recurso puede ser la interfaz a un servicio, a una impresora, a un dispositivo, etc.
¿QUÉ
ES…
?
Una Representación
Es una fotografía del estado de un recurso.
Cada vez que se solicita un recurso a través de HTTP, se retorna una representación de éste, no el recurso en sí.
¿QUÉ
ES…
?
Un Media Type
Es el formato específico que tiene una representación de un recurso.
Es el mecanismo a través del cual se comunican clientes y servidores el cómo entender la representación de un recurso al consumirlo.
¿QUÉ
ES…
?
HTTP
Es un protocolo, muy popular en Internet, que define el mecanismo a través del cuál clientes y servidores pueden compartir recursos.¿Q
UÉ E
S…?
URI Versión
Cabecera
Cuerpo--
Contenido
Método / Verbo
Ejem
plo
Ejem
plo Cabecer
a
Cuerpo--
Contenido
StatusVersión
Verbos o Métodos HTTP
Son componentes del protocolo HTTP que indican la acción que quiere realizar el cliente hacia el servidor con un recurso.
Actualmente hay cerca de veinte (20) diferentes métodos o verbos HTTP.
¿QUÉ
SON
…?
Ejem
plos
GET
POST
PUT
MERGE
Pide una representación del recurso especificado.
Envía los datos para que sean procesados por el recurso identificado.
Envía datos para actualizar un recurso identificado.
Acepta el cambio de estado de un recurso.
REST
Siglas en inglés para Representational State Transfer (Transferencia de Estado Representacional).
Es un estilo de arquitectura software para sistemas hipermedia distribuidos en una red, como por ejemplo Internet.
¿QUÉ
ES…
?
Un API RESTful no se construye.
Por el contrario, un API se considera RESTful, no tanto por las características que tiene, sino por cumplir una serie de restricciones.
Estas restricciones buscan crear plataformas que evolucionen a lo largo del tiempo...
…Que tolere diferentes tipos de clientes…
…Y que pueda introducir cambios sin romper dichos clientes.
Estas restricciones son las siguientes:
1. Los clientes (consumidores) están desacoplados del servidor (proveedor), permitiendo su evolución independiente.
2. El API no tiene estado (stateless). Cualquier requerimiento de conservación de estado debe ser gestionado por el cliente, y enviado al proveedor con cada petición.
3. Toda información dentro de una solicitud debe ser cacheable.
4. Utilizan una interfaz estandarizada para la interacción del sistema.
5. Cada mensaje (solicitud o respuesta) contiene toda la información necesaria por clientes y servidores para interactuar entre sí.
6. La manipulación de recursos es sólo y exclusivamente a través de su representación.
7. Sólo se emplean verbos y estados definidos por HTTP. Es el único protocolo empleado en la comunicación de mensajes entre clientes y servidores.
8. Son sistemas en capas. Estas capas son plataformas de abstracción que agregan capacidades de adaptación entre clientes y servidores.
9. Opcionalmente, permiten a los clientes obtener código ejecutable en sus instancias locales de forma dinámica.
Fuente Geek & Poke: http://geek-and-poke.com/geekandpoke/2013/6/14/insulting-made-easy
Versionado
Es el proceso de asignación de un nombre, código o número único, a un software para indicar su nivel de desarrollo¿Q
UÉ E
S…?
Permalink
Es un URI permanente a un recurso específico, para poder hacer referencia al mismo aún cuando su localización (URL) haya cambiado.Debido a que este URI es permanente, es menos susceptible de convertirse en un URI roto.
¿QUÉ
ES…
?
Versionando un Web API
Hablar de evolución no es más que emplear un término de moda para hacer referencia a los cambios.
Esto involucra entender que a través del tiempo surgirán modificaciones a lo largo de varios períodos de tiempo que harán irreconocible nuestra solución de lo que era en su momento original.
En el caso de Web APIs, este término puede hacer referencia a los siguientes puntos:• Los recursos podrían mantener mayor o menor
información.
• La representación de los recursos puede cambiar.
• La relación entre recursos puede cambiar.
• Nuevos recursos pueden ser agregados al API.
• El flujo de los procesos soportados por el API puede cambiar.
En general, se busca que un Web API sea evolutivo sin romper la compatibilidad y la integración con sus consumidores.
Para ello, los consumidores no pueden conocer los recursos de antemano. Deben describirlos en tiempo de ejecución con base a un único punto de entrada.
Así mismo, los consumidores deben ser capaces de determinar la forma de la representación del recurso a partir de metadata contenida en la respuesta.
Todo lo anterior implica que los consumidores del Web API deben crear clientes mucho más dinámicos y complejos que los tradicionales.
Los consumidores han de dotar a sus clientes del Web API de capacidades de detección de características, tanto de los recursos como de sus representaciones.
Lo único que tienen por seguro es que la URI del punto de entrada no cambiará con el tiempo.
Por su parte el proveedor debe construir mensajes auto-descriptivos para cada petición y para cada respuesta hacia sus recursos.
Igualmente, debe proveer metadata asociada a cada recurso para la identificación de las posibles representaciones.
En general, se considera que la combinación de descubrimiento de recursos en tiempo de ejecución, mensajes auto-descriptivos y clientes reactivos son críticos para conseguir un Web API evolucionable.
También se considera que, aunque son conceptos difíciles de entender e implementar, la flexibilidad y escalabilidad que aportan supera con creces su costo.
En general, en la filosofía REST, versionar recursos se considera una mala práctica o evidencia de un pobre diseño.
Pero en la vida real…
• Existe el time-to-market.
• Existe los cambios legales y administrativos.
• Existen los equipos con poca experiencia, mística profesional, seniority…
… con lo cual conviene pensar en la evolutibilidad del API, incluyendo en su diseño, la capacidad de soportar versionado.
Existen muchas estrategias de versionado de un Web API:TIPO DESCRIPCIÓN
Payload La versión es parte del mensaje.Query String La versión es un parámetro que forma parte de
la URI del recurso.
URL Suffix La versión es parte del URI, y se anexa al final del mismo.
Custom Header
La versión es suministrada en una cabecera propia no estándar.
URL La versión es parte indirecta del URI del recurso.Media Type
Content TypeLa versión es suministrada como el media type esperado de la representación del recurso.
TIPO DESCRIPCIÓNPayload La versión es parte del mensaje.
Query String La versión es un parámetro que forma parte de la URI del recurso.
URL Suffix La versión es parte del URI, y se anexa al final del mismo.
En Internet, se pueden encontrar intensos debates sobre cual tipo es mejor, cuál es más RESTful, cuál menos.
Existen argumentos a favor y encontrar de combinar varios tipos de estrategias.
Lo más conveniente es investigar por cuenta propia y escoger el tipo que mejor se adapte al proyecto o a los consumidores del API.
Ejem
plos
Payload
Query String
URL Suffix
<xml> <version>2</version> ...</xml>
http://MyCoolSite/api/recurso?v=2
http://MyCoolSite/api/recurso?v=1_1
http://MyCoolSite/api/recurso.v2
http://MyCoolSite/api/recurso.v1_1
Ejem
plos
Custom Header
URL
Media Type
GET http://MyCoolSite/api/recurso HTTP/1.1...api-version: 2...
http://MyCoolSite/api/v2/recurso
http://MyCoolSite/api/v1.1/recurso
GET http://MyCoolSite/api/recurso HTTP/1.1...Accept: application/vnd.company.site.api.resource.v2+json...
Ejem
plos www.facebook.com/v2.7/dialog/oauth
www.facebook.com/v2.7/plugins/
graph.facebook.com/v2.7/
Facebook emplea el tipo de versionado de su API por URL.
Ejem
plos https://api.twitter.com/1.1/statuses/user_timeline.json
https://api.twitter.com/1.1/direct_messages/destroy.json
https://api.twitter.com/1.1/users/report_spam.json
Twitter también emplea el tipo de versionado de su API por URL.
Ejem
plos Accept: application/vnd.github.v3+json
Accept: application/vnd.github.loki-preview+json
GitHub emplea el tipo de versionado de su API por Media Type en la cabecera Accept de HTTP.
Implementación
Al implementar el versionado de un REST API con ASP.NET Web API tenemos que tener claro la terminología.
El término “versión” en este contexto no aplica a la versión de un assembly (dll), sino a la versión conceptual de la interfaz pública del API.
Así, cambios en el assembly que implicarían un cambio de su versión, no necesariamente implica cambios en la versión del Web API…
…Mientras que cambios sobre aspectos propios del Web API implicarán un cambio de su versión.
Entre las acciones que provocan el cambio de versión de un assembly, pero no afectar a la versión del Web API tenemos:
• Corrección de errores o bugs.• Mejoras de rendimiento en las reglas de
negocio.• Ajustes en las reglas de negocio.
Entre las acciones que provocan el cambio de versión de un Web API tenemos:
• Cambios en los recursos.• Cambios en las representaciones de los recursos.• Cambios en los puntos de acceso (endpoints).• Corrección de errores o bugs.
Independientemente de la estrategia que se emplee de versionado, en ASP.NET Web API existen dos tendencias a la hora de escribir el código con soporte a versiones:
1. La versión es parte del nombre del controlador.
2. La versión es parte del espacio de nombres del controlador.
Version del Controlador por Nombrepublic class DummyController : ApiController{ ...}
public class DummyV2Controller : ApiController{ ...}
public class V2DummyController : ApiController{ ...}
public class DummyControllerV2 : ApiController{ ...}
public class DummyControllerV2 : ApiController{ // Este controlador no es válido, porque no cumple con la convención de ASP.NET MVC y // ASP.NET Web API sobre nombres para controladores. ... }
Versión de Controlador por Espacio de Nombres
namespace My.Cool.Api.V1{
public class DummyController : ApiController { ... }}
namespace My.Cool.Api.V2{
public class DummyController : ApiController { ... }}
El problema con este enfoque es que ASP.NET Web API localiza controladores por nombre de la clase, omitiendo el espacio de nombres; lo que implica extender la lógica de enrutamiento por defecto.
En esta presentación veremos como implementar el versionado a través de los siguientes tipos:
• Mediante URL• Mediante un custom header.• Mediante un custom media type.
Los ejemplos que veremos a lo largo de esta presentación seguirán la estrategia de versión como parte del nombre del controlador.
La estrategia de versión como parte del espacio de nombres del controlador es explorada con mucho más detalle en la presentación del Proyecto Power Rangers (PPR).
Código para los Ejemplos
public class Console{ public int Id { get; set; } public string Name { get; set; }} public class NexGenConsole : Console{ public bool IsNexGen { get; set; }}
Código para los Ejemplospublic class ConsoleController : ApiController{ public virtual IEnumerable<Console> GetConsoles { get { return ... } } public virtual Console GetConsole(int id) { return new Console { Id = id, Name = @"PS3" }; }}
public class ConsoleV2Controller : ConsoleController{ public override Console GetConsole(int id) { return new NexGenConsole { Id = id, Name = @"PS4", IsNexGen = true }; }}
Código para los Ejemplos
internal static class VersionFinder{ public static int GetVersionFromRequestData(HttpRequestMessage request) {...} private static bool NeedsUriVersioning(HttpRequestMessage request, out string version) { ... } private static bool NeedsHeaderVersioning(HttpRequestMessage request, out string version) { ... } private static bool NeedsAcceptVersioning(HttpRequestMessage request, out string version) { ... } private static int VersionToInt(string versionString) { ... }}
Versionado a través de Enrutamiento por Convención
Primero necesitamos crear un selector de controladores por versión…
…lo cual es relativamente sencillo, siempre y cuando se pueda localizar por su nombre para la versión correcta.
Para este caso es necesario recurrir a una implementación custom de la interfaz IHttpControllerSelector.
Versionado a través de Enrutamiento por Convención
La interfaz IHttpControllerSelector es el componente responsable de tomar la solicitud, y buscar el controlador correspondiente para gestionarla.
ASP.NET Web API provee una implementación por defecto, llamada DefaultHttpControllerSelector.
Versionado a través de Enrutamiento por Convención
La buena práctica establece que no se cree dicha implementación desde cero, sino extender la existente por defecto: DefaultHttpControllerSelector.
La clase DefaultHttpControllerSelector está pensada de forma tal que da preferencia al enrutamiento por atributos, haciendo coincidir la solicitud entrante con rutas de atributos registradas gracias al método interno (internal) GetDirectRouteCandidates.
Si no se encuentra coincidencias, el selector continúa con el proceso de buscar controladores a partir de las rutas por convención registradas programáticamente.
En cualquier caso, los controladores son identificados por esta clase a través de su nombre, no de su tipo.
Para esto, la clase DefaultHttpControllerSelector emplea el método GetControllerName para obtener el nombre único que servirá para identificar el tipo de controlador asociado al mismo.
Versionado a través de Enrutamiento por Convención
El método GetControllerName, que es virtual, ofrece excelentes puntos de extensibilidad para apoyar el versionado de controladores, y por ende del Web API.
Versionado a través de Enrutamiento por Convención
Así, se hará un override del método GetControllerName para que tome en cuenta información de versión de la solicitud para ubicar el nombre correcto del controlador.
Versionado a través de Enrutamiento por Convención
El método GetVersionedControllerName se encargará de construir el nombre del controlador apropiado acorde a la información de versión para poder ubicar el tipo correcto para atender la petición.
Versionado a través de Enrutamiento por Convención
Versionado a través de Enrutamiento por Convención
Luego, es importante reemplazar la implementación por defecto por la implementación custom del selector de controladores.public static class WebApiConfig{ public static void Register(HttpConfiguration config, IDependencyResolver dependencyResolver) { if (config != null) { ... config.Services.Replace(typeof(IHttpControllerSelector), new VersionAwareControllerSelector(config));
... } }}
Versionado a través de Enrutamiento por ConvenciónFinalmente, es necesario establecer los convenios
de enrutamiento.public static class WebApiConfig{ public static void Register(HttpConfiguration config, IDependencyResolver dependencyResolver) { if (config != null) { ... config.Routes.MapHttpRoute(name: @"VersionedApi", routeTemplate: @"api/v{version}/{controller}/{id}", defaults: new { id = RouteParameter.Optional }, constraints: new { version = @"[1-9]+[\d]*" });
config.Routes.MapHttpRoute(name: @"DefaultApi", routeTemplate: @"api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }); ... } }}
DEMO 1
Versionado a través de Enrutamiento por Atributos
Para este escenario no es necesario una implementación custom de la interfaz IHttpControllerSelector…
…Sino que se fundamenta en la implementación de la interfaz IDirectRouteFactory...
…Y la extensión de la clase RouteFactoryAttribute.
Versionado a través de Enrutamiento por AtributosUno de los primeros pasos es decorar los
controladores con los atributos de enrutamiento apropiados.[RoutePrefix(@"api/v1")]
public class ConsoleAttributeController : ApiController{ [Route(@"consoles")]
public virtual IEnumerable<Console> Console RetrieveConsoles() { return ... } [Route(@"consoles/{id:int}")]
public virtual Console RetrieveConsole(int id) { return new Console { Id = id, Name = @"PS3" }; }
}
Versionado a través de Enrutamiento por Atributos
[RoutePrefix(@"api/v2")]public class ConsoleAttributeV2Controller : ConsoleAttributeController{ [Route(@"consoles/{id:int}")] public override Console RetrieveConsole(int id) { return new NexGenConsole { Id = id, Name = @"PS4", IsNexGen = true }; }
}
Y ya sólo con esto, se tendría versionado mediante URL, ya que la declaración de la ruta directamente en el controlador o la acción, es la naturaleza misma del enrutamiento por atributo.
DEMO 2
Versionado a través de Enrutamiento por Atributos
Soportar las modalidades de custom header y custom media type, simplemente requiere identificar el origen del dato correspondiente a la versión.
Esta información la obtendremos en ambos casos de los datos contenidos en instancias de la clase HttpRequestMessage.
Y para esto, ya contamos con la clase VersionFinder.
Versionado a través de Enrutamiento por Atributos
Sin embargo, es necesario informar a los atributos de enrutamiento de la existencia de una restricción por versión, para lo cual:
1. Implementar la interfaz IHttpRouteConstraint.
2. Crear un nuevo atributo que extienda de RouteFactoryAttribute y que aplique la restricción del primer punto.
Versionado a través de Enrutamiento por Atributos
internal class VersionConstraint : IHttpRouteConstraint{ private readonly int allowedVersion; public VersionConstraint(int version) { this.allowedVersion = version; } public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection) { return request == null ? false : this.allowedVersion == VersionFinder.GetVersionFromRequestData(request); }}
Versionado a través de Enrutamiento por Atributos
internal class VersionedRouteAttribute : RouteFactoryAttribute{ public VersionedRouteAttribute(string template) : base(template) { this.Order = -1; } public int Version { get; set; } public override IDictionary<string, object> Constraints { get { return new HttpRouteValueDictionary { { string.Empty, new VersionConstraint(this.Version) } }; } }}
Versionado a través de Enrutamiento por AtributosFinalmente, decoramos los controladores con el
nuevo atributo VersionedRouteAttribute.[RoutePrefix(@"api")]public class ConsoleAttributeController : ApiController{ [HttpGet, Route(@“v1/consoles")] [VersionedRoute(@"consoles", Version = 1)] public virtual IEnumerable<Console> Console Get() { return ... } [HttpGet, Route(@“v1/consoles/{id:int}")] [VersionedRoute(@“consoles/{id:int}", Version = 1)] public virtual Console Get(int id) { return new Console { Id = id, Name = @"PS3" }; }}
Versionado a través de Enrutamiento por Atributos
[RoutePrefix(@"api")]public class ConsoleAttributeV2Controller : ConsoleAttributeController{ [HttpGet, Route(@"v2/consoles")] [VersionedRoute(@"consoles", Version = 2)] public override IEnumerable<Console> RetrieveConsoles() { return base.RetrieveConsoles(); }
[HttpGet, Route(@"v2/consoles/{id:int}")] [VersionedRoute(@"consoles/{id:int}", Version = 2)] public override Console Get(int id) { return new NexGenConsole { Id = id, Name = @"PS4", IsNexGen = true }; }}
Y ahora se cuenta con versionado por URL, plus versionado por custom header y custom media type.
DEMO 3
RECOMENDACIONESY
BUENAS PRÁCTICAS
Encontraremos diferentes posiciones filosóficas sobre la “manera correcta” de alcanzar REST, sobre qué es RESTful, y qué no lo es.
Desafortunadamente, lo filosófico pasa a lo religioso y se pierde el foco sobre cuál debería ser el objetivo real: construir software que funcione y un API coherente que permita consumirle e integrarle fácilmente.
1. El modelo de datos debe estar estabilizado antes de comenzar a diseñar el Web API.
2. Diseñar, preservar y defender un contrato de integración estable y coherente.
3. Siempre tener una estrategia coherente de versionado, que garantice la gestión y la preservación de un contrato estable con los consumidores del Web API.
4. Proporcionar más de un mecanismo o estrategia de versionado.
5. Al versionar, emplear siempre números enteros.
6. El número de versión de un Web API no tiene necesariamente que mapear al número de versión del software que expone.
7. Los recursos pueden ser aumentados, enriquecidos, extendidos, pero nunca deben cambiar.
8. Un Web API es una interfaz de usuario donde los usuarios son desarrolladores. Siempre es conveniente poner un esfuerzo importante en hacer dicha interfaz placentera.
9. Conviene diseñar y establecer una política clara de depreciación de versiones.
10. Nunca usar alias. La excepción son los permalinks.
11. Usar correctamente los verbos y los códigos HTTP.
12. Informar a los consumidores de los cambios de versión.
13. Un Web API es tan bueno como lo sea su documentación. Por tanto, hay que contar y suministrar la mejor documentación.
http://shonzilla/api/customers/1234
http://shonzilla/api/v3.0/customers/1234
http://shonzilla/api/v3/customers/1234
Literatura y Bibliografía
• Excelente presentación de los aspectos teóricos de las RESTful APIs.
• Es el más teórico de los libros disponibles en el mercado.
• Ejemplos muy completos en .NET
• Muestra el proceso y mecanismo para conseguir una Web API 100% evolucionable y 100% RESTful.
• El libro más completo en cuanto a los aspectos técnicos de ASP.NET Web API.
• Excelentes ejemplos técnicos, aunque algunos se quedan en Hello Worlds.
• Incluye una sección donde explican el versionado de las Web APIs.
• El ejemplo de versionado sigue la tendencia por espacio de nombres.
• El más practico de la literatura disponible.
• Tiene dos secciones completas que explican con ejemplos muy prácticos el versionado por convención o por atributos.
• Todos los ejemplos de versionado siguen la tendencia por nombre de controlador.
Preguntas
¡GRACIAS!
@rliberoff
https://es.linkedin.com/in/rliberoffFuente CommitStrip: http://www.commitstrip.com/en/2013/08/20/numeros-de-version/