Control de versiones de una API REST

1. El problema

La evolución de una API REST es un problema difícil, para el que hay muchas opciones disponibles. Este artículo analiza algunas de estas opciones.

2. ¿Qué hay en el contrato?

Antes que nada, debemos responder una pregunta simple: ¿Qué es el contrato entre la API y el cliente?

2.1. ¿Los URI forman parte del contrato?

Primero consideremos la estructura de URI de la API REST : ¿es parte del contrato? ¿Deben los clientes marcar, codificar y, en general, confiar en los URI de la API?

Si este es el caso, entonces la interacción del Cliente con el Servicio REST ya no sería impulsada por el Servicio en sí, sino por lo que Roy Fielding llama información fuera de banda :

Se debe ingresar una API REST sin conocimiento previo más allá del URI inicial (marcador) y un conjunto de tipos de medios estandarizados que son apropiados para la audiencia prevista ... El fracaso aquí implica que la información fuera de banda está impulsando la interacción en lugar del hipertexto.

¡Así que claramente los URI no son parte del contrato ! El cliente solo debe conocer un único URI: el punto de entrada a la API. Todos los demás URI deben descubrirse mientras se consume la API.

2.2. ¿Los tipos de medios forman parte del contrato?

¿Qué pasa con la información del Tipo de medio utilizada para las representaciones de los Recursos? ¿Son parte del contrato entre el Cliente y el Servicio?

Para consumir con éxito la API, el Cliente debe tener conocimiento previo de estos Tipos de Medios . De hecho, la definición de estos tipos de medios representa todo el contrato.

Por lo tanto, aquí es donde el servicio REST debería enfocarse más:

Una API REST debería dedicar casi todo su esfuerzo descriptivo a definir los tipos de medios utilizados para representar recursos y controlar el estado de la aplicación, o definir nombres de relaciones extendidas y / o marcas habilitadas para hipertexto para tipos de medios estándar existentes.

Por lo que las definiciones de los tipos de medios son parte del contrato y deben ser de conocimiento previo para el cliente que consume la API. Aquí es donde entra la estandarización.

Ahora tenemos una buena idea de qué es el contrato, pasemos a cómo abordar realmente el problema de las versiones.

3. Opciones de alto nivel

Analicemos ahora los enfoques de alto nivel para versionar la API REST:

  • Control de versiones de URI: versione el espacio de URI usando indicadores de versión
  • Control de versiones del tipo de medio : versión de la representación del recurso

Cuando introducimos la versión en el espacio URI, las Representaciones de Recursos se consideran inmutables. Por tanto, cuando es necesario introducir cambios en la API, es necesario crear un nuevo espacio URI.

Por ejemplo, supongamos que una API publica los siguientes recursos: usuarios y privilegios:

//host/v1/users //host/v1/privileges

Ahora, consideremos que un cambio radical en la API de los usuarios requiere la introducción de una segunda versión:

//host/v2/users //host/v2/privileges

Cuando versionamos el tipo de medio y ampliamos el idioma, pasamos por la negociación de contenido según este encabezado. La API REST utilizaría tipos de medios MIME de proveedores personalizados en lugar de tipos de medios genéricos como application / json . Vamos a versionar estos tipos de medios en lugar de los URI.

Por ejemplo:

===> GET /users/3 HTTP/1.1 Accept: application/vnd.myname.v1+json <=== HTTP/1.1 200 OK Content-Type: application/vnd.myname.v1+json { "user": { "name": "John Smith" } }

Podemos consultar este artículo "Tipos de medios personalizados para API de resto" para obtener más información y ejemplos sobre este tema.

Lo que es importante entender aquí es que el cliente no hace suposiciones sobre la estructura de la respuesta más allá de lo definido en el tipo de medio.

Es por eso que los tipos de medios genéricos no son ideales. Estos no proporcionan suficiente información semántica y obligan al cliente a utilizar pistas adicionales para procesar la representación real del recurso.

Una excepción a esto es utilizar alguna otra forma de identificar de forma única la semántica del contenido, como un esquema XML.

4. Ventajas y desventajas

Ahora que tenemos un concepto claro de lo que es parte del Contrato entre el Cliente y el Servicio, así como una descripción general de alto nivel de las opciones para la versión de la API, analicemos las ventajas y desventajas de cada enfoque.

Primero, la introducción de identificadores de versión en el URI conduce a una huella de URI muy grande. Esto se debe al hecho de que cualquier cambio importante en cualquiera de las API publicadas introducirá un árbol de representaciones completamente nuevo para toda la API. Con el tiempo, esto se convierte en una carga de mantenimiento y en un problema para el cliente, que ahora tiene más opciones entre las que elegir.

Los identificadores de versión en el URI también SON muy inflexibles . No hay forma de simplemente evolucionar la API de un solo recurso o un pequeño subconjunto de la API general.

Como mencionamos antes, este es un enfoque de todo o nada. Si parte de la API se mueve a la nueva versión, entonces toda la API debe moverse con ella. Esto también hace que la actualización de los clientes de la v1 a la v2 sea una tarea importante, lo que conduce a actualizaciones más lentas y períodos de puesta de sol mucho más largos para las versiones anteriores.

El almacenamiento en caché HTTP también es una preocupación importante cuando se trata de versiones.

Desde la perspectiva de las cachés de proxy en el medio, cada enfoque tiene ventajas y desventajas. Si el URI tiene una versión, la caché deberá mantener varias copias de cada recurso, una para cada versión de la API. Esto carga la caché y disminuye la tasa de aciertos de la caché, ya que diferentes clientes usarán diferentes versiones.

Also, some cache invalidation mechanisms will no longer work. If the media type is the one that is versioned, then both the Client and the Service need to support the Vary HTTP header to indicate that there are multiple versions being cached.

From the perspective of client caching however, the solution that versions the media type involves slightly more work than the one where URIs contain the version identifier. This is because it's simply easier to cache something when its key is an URL than a media type.

Let's end this section with defining some goals (straight out of API Evolution):

  • keep compatible changes out of names
  • avoid new major versions
  • makes changes backwards-compatible
  • think about forwards-compatibility

5. Possible Changes to the API

Next, let's consider the types of changes to the REST API – these are introduced here:

  • representation format changes
  • resource changes

5.1. Adding to the Representation of a Resource

The format documentation of the media type should be designed with forward compatibility in mind. Specifically, a client should ignore information that it doesn't understand (which JSON does better than XML).

Now, adding information in the Representation of a resource will not break existing clients if these are correctly implemented.

To continue our earlier example, adding the amount in the representation of the user will not be a breaking change:

{ "user": { "name": "John Smith", "amount": "300" } }

5.2. Removing or Changing an Existing Representation

Removing, renaming or generally restructuring information in the design of existing representations is a breaking change for clients. This is because they already understand and rely on the old format.

This is where Content Negotiation comes in. For such changes, we can add a new vendor MIME media type.

Let's continue with the previous example. Say we want to break the name of the user into firstname and lastname:

===> GET /users/3 HTTP/1.1 Accept: application/vnd.myname.v2+json <=== HTTP/1.1 200 OK Content-Type: application/vnd.myname.v2+json { "user": { "firstname": "John", "lastname": "Smith", "amount": "300" } }

As such, this does represent an incompatible change for the Client – which will have to request the new Representation and understand the new semantics. However, the URI space will remain stable and will not be affected.

5.3. Major Semantic Changes

These are changes in the meaning of the Resources, the relations between them or what the map to in the backend. This kind of changes may require a new media type, or they may require publishing a new, sibling Resource next to the old one and making use of linking to point to it.

While this sounds like using version identifiers in the URI all over again, the important distinction is that the new Resource is published independently of any other Resources in the API and will not fork the entire API at the root.

The REST API should adhere to the HATEOAS constraint. According to this, most of the URIs should be DISCOVERED by Clients, not hardcoded. Changing such an URI should not be considered an incompatible change. The new URI can replace the old one and Clients will be able to re-discover the URI and still function.

It's worth noting however that, while using version identifiers in the URI is problematic for all of these reasons, it is not un-RESTful in any way.

6. Conclusion

This article tried to provide an overview of the very diverse and difficult problem of evolving a REST Service. We discussed the two common solutions, advantages and disadvantages of each one, and ways to reason about these approaches in the context of REST.

El artículo concluye defendiendo la segunda solución: versionar los tipos de medios mientras se examinan los posibles cambios en una API RESTful.

La implementación completa de este tutorial se puede encontrar en el proyecto GitHub.

7. Lecturas adicionales

Por lo general, estos recursos de lectura están vinculados a lo largo del artículo, pero en este caso, simplemente hay demasiados buenos:

    • Las API REST deben estar impulsadas por hipertexto
    • Evolución de API
    • Vinculación para una API HTTP
    • Estrategias de compatibilidad