38. 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.
39. En general, en la filosofía REST, versionar recursos se
considera una mala práctica o evidencia de un pobre
diseño.
40.
41. 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 Type
La versión es suministrada como el media type
esperado de la representación del recurso.
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.
54. Version del Controlador por Nombre
public 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.
...
}
55. 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
{
...
}
}
56.
57.
58. 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; }
}
59. Código para los Ejemplos
public 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 };
}
}
60. 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) { ... }
}
70. Versionado a través de Enrutamiento por Convención
public static class WebApiConfig
{
public static void Register(HttpConfiguration config, IDependencyResolver dependencyResolver)
{
if (config != null)
{
...
config.Services.Replace(typeof(IHttpControllerSelector),
new VersionAwareControllerSelector(config));
...
}
}
}
71. Versionado a través de Enrutamiento por Convención
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 });
...
}
}
}
74. Versionado a través de Enrutamiento por Atributos
Uno 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" };
}
}
75. 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.
77. 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.
78. 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:
79. 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);
}
}
80. 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) }
};
}
}
}
81. Versionado a través de Enrutamiento por Atributos
Finalmente, 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" };
}
}
82. 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.
85. 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.
¿Quién sabe qué es esto?Les presento a INTERNET, tal y como existía en su primera versión en 1977 cuando se llamaba ARPANET.
En 1989, Tim Berners-Lee, un científico del CERN, inventó la World Wide Web, un sistema de acceso a los documentos vinculados a través de Internet. El acceso a estos documentos requería un protocolo para acceder (o navegar) hacia y entre ellos.
Dicho protocolo se conoce hoy día como el protocolo de transferencia de hipertexto (HTTP). Este protocolo es en el centro de lo que impulsa sitios web y APIs Web.
Este es Internet hoy día…En un principio podemos pensar que es una colección de sitios web, de páginas que podemos visitar, donde ver información útil, donde tenemos acceso a una ventana a la vida – que nos quieran mostrar – los demás.
Es mucho más que eso…
Todos esos sitios, en su mayoría, nos ofrecen un API para interactuar con ellos. Expanden los límites de su funcionalidad y de lo que ofrecen a través de un API que nos permite explotar aún más sus capacidades.
La actual tendencia, en temas de API en Internet, son las Web API, también conocidas como API REST.
Piensen en cualquiera de sus sitios web favoritos: Facebook, 9GAG, Twitter… todos exponen alguna forma de API, y actualmente todos son REST.
¿Saben de qué es la foto? Es de la misión Apollo 13, donde los ingenieros trataban de encontrar la manera de encajar un filtro de aire (CO2) de forma cúbica en un agujero de forma cilíndrica.
El cambio es inevitable. Los cambios en una Web API son inevitables a medida que el conocimiento y la experiencia que se adquiere sobre un sistema y el negocio al que atienden mejora y se incrementa.
Gestionar el impacto de estos cambios ciertamente puede ser un reto, sobre todo cuando aparece la amenaza de romper la integración que se tenga con los clientes del Web API.
Estas son las preguntas alrededor de las cuales realizar el diseño de la Web API.
A primera vista se podría pensar a un enfoque en cascada, pero no es en absoluto el caso. No se trata de diseñar todo el API antes de su construcción; no es una receta
para la parálisis por análisis.
Definitivamente, hay decisiones que debe tomar por adelantado, pero que son de alto nivel y se relacionan con el diseño del API general. No requieren entender
o predecir todos los aspectos del sistema. Más bien, estas decisiones han de sentar las bases para que el API tenga la capacidad de evolucionar de forma iterativa y orgánica.
De una petición a la siguiente, el estado de los recursos puede cambiar drásticamente, por lo que la representación que se devuelve puede ser muy diferente.
Ejemplos de media types son:
text/html
text/xml
image/png
application/json
Los media types son identificados con dos componentes: la primera corresponde al nivel superior que describe de forma genérica el tipo de información; y la segunda denominada subtipo y que describe el formato concreto y su estructura de datos esperada.
Si bien el término REST se refiere originalmente a un conjunto de principios de arquitectura, hoy día se usa en un sentido más amplio para describir cualquier interfaz entre sistemas que utilice directamente el protocolo y los verbos HTTP para obtener datos o indicar la ejecución de operaciones sobre los datos, en cualquier formato (XML, JSON, etc) sin las abstracciones adicionales de los protocolos basados en patrones de intercambio de mensajes, como por ejemplo SOAP.
A servicios web públicamente disponibles en Internet se les suele denominar como servicios RESTful.
En esencia, hablar de Servicios REST o de Interfaces RESTful es hablar de lo mismo.
En el mundo .NET solemos llamar a los servicios REST como Web APIs.
En Web APIs se emplea permalinks para apuntar a la versión más reciente.
Por ejemplo, hace unos años la representación estándar era XML, hoy es JSON, mañana puede ser otra cosa totalmente diferente…
Esto en principio no es nada descabellado, ya que es justamente como funcionan los navegadores web.
Desde la perspectiva de desarrollo de sistemas capaces de evolucionar, es útil tener considerar el versionado como el último recurso, cómo una admisión de fracaso.
Asignar un “V1” al API inicial es proclamar (casi a gritos) que ya sabe que el API no puede evolucionar, y que surgirán cambios rompedores cuando se publique la versión “V2”.
Sin embargo, todos somos humanos, y como tal nos equivocamos. En este caso, el emitir versiones es lo que hacemos cuando hemos agotado todas las demás opciones.
URL Es el mecanismo más directo y más popular. No obstante es uno de los que más molesta a los puristas de REST, ya que insisten (y tienen razón) que un URI debe referir a un recurso único y distinguido.
Custom Header y Media Content Un custom header permite preservar las URIs entre versiones, a pesar de ser efectivamente una duplicidad del comportamiento ya implementado y existente en HTTP de negociación del contenido (content negotiation). Lo anterior se evita justamente empleando la cabecera Accept de HTTP, con lo cual se evita la mencionada duplicidad.
Sin embargo, en ambos casos, se fuerza a los clientes a conocer de ante mano que cabeceras deben enviar en una solicitud para especificar la correcta versión del recurso que desean acceder.
Y Ciertamente, en ambos cosas, se permite preservar un conjunto limpio y claro de URLs hacia los recursos, pero agrega la necesidad de lidiar con la complejidad de servir diferentes versiones de contenido en algún punto del código del Web API. A veces, y dependiendo de la tecnología, esta complejidad termina llegando a la implementación del controlador, que se más allá de sólo encargarse del procesamiento y atención del recurso, termina también siendo responsable de determinar que versión del mismo (y su representación) enviar al llamador.
Así, cambios en el código de un ensamblado (por ejemplo por corrección de errores o bugs) implicará un cambio de versión del mismo…
…Pero no necesariamente implica un cambio en el API que contiene, ya que no hay cambios en la interfaz pública.
Cambios en los recursos, sus representaciones o sus puntos de acceso si implicará un cambio del API, y también de su correspondiente ensamblado.
Planos del proyecto cancelado para la autovía de la Bahía de Seattle.
Coherente es la palabra clave a lo largo de esta parte de la presentación…
El modelo de datos debe estar estabilizado antes de comenzar a diseñar el Web API. Esto ayudará a minimizar la generación espontanea y acelerada de versiones por imponderables o cosas “que no se pensaron o tuvieron en cuenta”.
Si los consumidores invierten tiempo (y por ende dinero y esfuerzo) en escribir código que permita emplear nuestro Web API, entonces nosotros debemos hacer el máximo esfuerzo por no romper esa integración.
Siempre manteniendo la línea de la coherencia. Ejemplo: combinar URL con custom media type.
En la medida de lo posible evitar “1.1”, “2.2.3”.
El software puede evolucionar en otras versiones debido a correcciones de errores o bugs, o mejoras de rendimiento, o cambios en sus tecnologías inherentes (como frameworks o bases de datos). Pero el Web API como tal, sus recursos y representaciones no han cambiado, por lo cual no requieren versionarse de nuevo.
En los principios SOLID, el principio “Open\Closed” de Bertrand Meyer sugiere que las entidades y componentes de un software deben ser abiertas (open) para su extensión, y cerradas (closed) para su modificación. Esto llevado al contexto de los Web APIs tiene la implicación de que un recurso no debe cambiar arbitrariamente su firma, sino más bien extenderse (son sobrecargas por ejemplo) para soportar nuevos parámetros o restricciones.
Por ejemplo, Facebook garantiza que cada versión de su Web API durará al menos dos años, y será removida pasados dos años tras la emisión de una versión más nueva. Por ejemplo, si la versión v2.3 es liberada el 25 de marzo, 2015, y la versión de v2.4 se libera el 7 de Agosto de 2015, entonces la versión v2.3 dejará de estar disponible el 7 de Agosto de 2017, exactamente dos años después de la emisión de la versión v2.4.Este tipo de políticas deben estar correctamente documentadas y públicas.
Por ejemplo, si empleamos versionado por URL, cuando un consumidor accede a un recurso viejo, se podría contestar con códigos HTTP 301 o 302 según convenga, para informar del cambio de localización del recurso debido a una nueva versión del mismo. Así:
301 Moved Permanently para indicar que el recurso en el URI de la solicitud se ha movido permanentemente a otro URI, el cual debería ser un permalink sin información de versión a la instancia de la nueva versión del recurso). Este código de estado puede emplearse para indicar una versión del Web API obsoleta o no soportada, informando al consumidor del reemplazo por la nueva versión.
302 Found para indicar que el recurso solicitado está temporalmente localizado en otro sitio, y que por lo tanto el URI sigue siendo soportado. Este código de estado puede ser útil cuando URIs sin información de versión (permalinks) no estén disponibles y se desee informar a los consumidores que aún así la URI es correcta, que serán re-direccionados temporalmente a una URI con información de versión, pero que deberían seguir empleando la URI original.