Si no está muy familiarizado con las API, es posible que se pregunte ... ¿por qué tanto alboroto por el control de versiones de API?
Si ha sido quemado por los cambios de API, probablemente sea usted el que se preocupa. Si es un mantenedor de una API, es posible que también se preocupe por tratar de responder preguntas desafiantes como estas:
# Is this version 2 of just products or of the entire API? /v2/products # What catalyzed the change between v1 and v2? How are they different? /v1/products /v2/products
Estas preguntas sobre el control de versiones no son fáciles de responder. No siempre está claro en cuanto a qué v1
o v2
se refiere. Y no deberíamos simplemente hacer una segunda versión de un punto final cuando la primera ya no parece ser suficiente.
Existen razones claras por las que su API necesita tener versiones, y existen estrategias claras sobre cómo navegar de manera efectiva por los cambios de API.
Sin embargo, he descubierto que la mayoría de los desarrolladores, incluido yo mismo, hasta que aprendí algunas lecciones por las malas, no son conscientes de estas razones y estrategias.
Este artículo busca resaltar esas razones para el control de versiones y las estrategias para lograrlo. Asumiremos un contexto de API REST, ya que es un estándar para muchas API, y nos centraremos en el aspecto del control de versiones .
¿Qué es el control de versiones?
Deberíamos comenzar con la configuración de nivel sobre lo que se entiende por el término "control de versiones de API". Aquí está nuestra definición de trabajo:
El control de versiones de API es la práctica de administrar de manera transparente los cambios en su API.El control de versiones es una comunicación eficaz en torno a los cambios en su API, para que los consumidores sepan qué esperar de ella. Está entregando datos al público de alguna manera y necesita comunicarse cuando cambia la forma en que se entregan los datos.
En resumen, todo esto se reduce a gestionar los contratos de datos y romper los cambios. El primero es el componente principal de su API y el segundo revela por qué es necesario el control de versiones.
Contratos de datos
Una API es una interfaz de programación de aplicaciones y una interfaz es un límite compartido para intercambiar información. El contrato de datos es el corazón de esta interfaz.
Un contrato de datos es un acuerdo sobre la forma y el contenido general de los datos de solicitud y / o respuesta.Para ilustrar un contrato de datos, aquí hay un cuerpo de respuesta JSON básico:
{ "data": [ { "id": 1, "name": "Product 1" }, { "id": 2, "name": "Product 2" } ] }
Es un objeto con una data
propiedad que es una matriz (lista) de productos, cada uno con una propiedad id
y name
. Pero la data
propiedad podría haberse llamado con la misma facilidad body
y la id
propiedad de cada producto podría haber sido un GUID en lugar de un número entero. Si se devuelve un solo producto, data
podría ser un objeto en lugar de una matriz.
Estos cambios aparentemente sutiles habrían dado lugar a un acuerdo diferente, un contrato diferente, con respecto a la "forma" de los datos. La forma de los datos podría aplicarse a nombres de propiedades, tipos de datos o incluso al formato esperado (JSON frente a XML).
¿Por qué es necesario el control de versiones?
Con las API, algo tan simple como cambiar el nombre de una propiedad de productId
a productID
puede romper cosas para los consumidores. Esto mismo le pasó a nuestro equipo la semana pasada.
Afortunadamente, realizamos pruebas para detectar cambios en el contrato de API. Sin embargo, no deberíamos haber necesitado esas pruebas, porque los mantenedores de la API deberían haber sabido que esto sería un cambio rotundo.
Cambios importantes
Este fue un cambio rotundo en el contrato de datos acordado porque su cambio nos obligó a cambiar también nuestra aplicación.
¿Qué constituye un "cambio radical" en un punto final de API? Cualquier cambio en su contrato de API que obligue al consumidor a realizar también un cambio.Los cambios importantes se encuadran principalmente en las siguientes categorías:
- Cambiar el formato de solicitud / respuesta (por ejemplo, de XML a JSON)
- Cambiar el nombre de una propiedad (por ejemplo, de
name
aproductName
) o el tipo de datos en una propiedad (por ejemplo, de un número entero a un flotante) - Agregar un campo obligatorio en la solicitud (por ejemplo, un nuevo encabezado o propiedad obligatorios en el cuerpo de una solicitud)
- Eliminar una propiedad en la respuesta (por ejemplo, eliminar
description
de un producto)
Gestión de cambios de API
Nunca es prudente ni amable obligar a los consumidores de una API a realizar un cambio. Si debe realizar un cambio importante, para eso está el control de versiones, y cubriremos las formas más efectivas de crear versiones de su aplicación y puntos finales.
Pero primero, analicemos brevemente cómo evitar cambios importantes en primer lugar. Podríamos llamar a esto gestión de cambios de API.
La gestión de cambios efectiva en el contexto de una API se resume en los siguientes principios:
- Continuar el soporte para propiedades / puntos finales existentes
- Agregue nuevas propiedades / puntos finales en lugar de cambiar los existentes
- Elimina cuidadosamente las propiedades / los puntos finales obsoletos
A continuación, se muestra un ejemplo que demuestra estos tres principios en el contexto de la respuesta para solicitar datos de usuario:
{ "data": { "id": 1, "name": "Carlos Ray Norris", // original property "firstName": "Carlos", // new property "lastName": "Norris", // new property "alias": "Chuck", // obsolete property "aliases": ["Chuck", "Walker"] // new property }, "meta": { "fieldNotes": [ { "field": "alias", "note": "Sunsetting on [future date]. Please use aliases." } ] } }
En este ejemplo, name
era una propiedad original. Los campos firstName
y lastName
se están implementando para proporcionar una opción más granular, en caso de que el consumidor quiera mostrar "Mr. Norris" con alguna interpolación de cadenas pero sin tener que analizar el name
campo. Sin embargo, la name
propiedad será respaldada de manera continua.
alias
, por otro lado, se desaprobará en favor de la aliases
matriz, porque Chuck tiene muchos alias, y hay una nota en la respuesta para indicar el período de tiempo de finalización.
¿Cómo se versiona una API?
Estos principios recorrerán un largo camino para navegar por los cambios en su API sin necesidad de lanzar una nueva versión. Sin embargo, a veces se puede evitar y, si necesita un contrato de datos completamente nuevo, necesitará una nueva versión de su punto final. Por lo tanto, deberá comunicarlo al público de alguna manera.
Aparte, tenga en cuenta que no estamos hablando de la versión del código base subyacente. Por lo tanto, si utiliza versiones semánticas para su aplicación que también admite una API pública, es probable que desee separar esos sistemas de versiones.
¿Cómo se crea una nueva versión de su API? ¿Cuáles son los diferentes métodos para hacerlo? Deberá determinar qué tipo de estrategia de control de versiones desea adoptar en general y, luego, a medida que desarrolle y mantenga su API, deberá determinar el alcance de cada cambio de versión.
Alcance
Primero abordemos el alcance. Como exploramos anteriormente, a veces los contratos de datos se verán comprometidos por un cambio importante, y eso significa que tendremos que proporcionar una nueva versión del contrato de datos. Eso podría significar una nueva versión de un punto final o podría significar un cambio en un ámbito de aplicación más global.
Podemos pensar en los niveles de cambio de alcance dentro de una analogía de árbol:
- Hoja : un cambio en un punto final aislado sin relación con otros puntos finales
- Rama : un cambio en un grupo de puntos finales o un recurso al que se accede a través de varios puntos finales
- Troncal : un cambio a nivel de aplicación, que garantiza un cambio de versión en la mayoría o en todos los puntos finales
- Raíz : un cambio que afecta el acceso a todos los recursos de la API de todas las versiones
Como puede ver, al pasar de la hoja a la raíz, los cambios se vuelven progresivamente más impactantes y de alcance global.
El alcance de la hoja a menudo se puede manejar mediante una gestión de cambios de API eficaz. Si no es así, simplemente cree un nuevo punto final con el nuevo contrato de datos de recursos.
A branch is a little trickier, depending on just how many endpoints are affected by the data contract change on the resource in question. If the changes are relatively confined to a clear group of related endpoints, you could potentially navigate this by introducing a new name for the resource and updating your docs accordingly.
# variants, which has a breaking change, is accessed on multiple routes /variants /products/:id/variants # we introduce product-variants instead /product-variants /products/:id/product-variants
A trunk refers to application-level changes that are often a result of a change in one of the following categories:
- Format (e.g. from XML to JSON)
- Specification (e.g. from an in-house one to JSON API or Open API)
- Required headers (e.g. for authentication/authorization)
These will necessitate a change in your overall API version, so you should plan carefully and execute the transition well.
A root change will force you to go one step further in ensuring that all consumers of all versions of your API are aware of the change.
Types of API Versioning
As we turn to different types of API versioning, we'll want to use these insights into varying scopes of API changes to evaluate the types. Each approach has its own set of strengths and weaknesses in addressing changes based on their scope.
There are several methods for managing the version of your API. URI path versioning is the most common.
URI Path
//www.example.com/api/v1/products //api.example.com/v1/products
This strategy involves putting the version number in the path of the URI, and is often done with the prefix "v". More often than not, API designers use it to refer to their application version (i.e. "trunk") rather than the endpoint version (i.e. "leaf" or "branch"), but that's not always a safe assumption.
URI path versioning implies orchestrated releases of application versions that will require one of two approaches: maintaining one version while developing a new one or forcing consumers to wait for new resources until the new version is released. It also means you'd need to carry over any non-changed endpoints from version to version. However, for APIs with relatively low volatility, it's still a decent option.
You would likely not want to relate your version number to that of the endpoint or resource, because it would easily result in something like a v4
of products
but a v1
of variants
, which would be rather confusing.
Query Params
//www.example.com/api/products?version=1
This type of versioning adds a query param to the request that indicates the version. Very flexible in terms of requesting the version of the resource you'd like at the "leaf" level, but it holds no notion of the overall API's version and lends itself to the same out-of-sync issues mentioned in the above comment on endpoint-level versioning of the URI path.
Header
Accept: version=1.0
The header approach is one that provides more granularity in serving up the requested version of any given resource.
However, it's buried in the request object and isn't as transparent as the URI path option. It's also still hard to tell whether 1.0
refers to the version of the endpoint or the API itself.
Integrating Types
Each of these approaches seem to have the weakness of either favoring a "leaf" or "trunk" scope, but not supporting both.
If you need to maintain the overall API version and also provide support for multiple versions of resources, consider a blend of the URI Path and Query Params types, or a more advanced Header approach.
# URI path and query params combo //api.example.com/v1/products?version=1 //api.example.com/v1/products?version=2 # Extended headers, for //api.example.com/products Accept: api-version=1; resource-version=1 Accept: api-version=1; resource-version=2
Conclusion
We've covered a lot of ground here, so let's recap:
- API versioning is the practice of transparently managing changes to your API.
- Managing an API boils down to defining and evolving data contracts and dealing with breaking changes.
- The most effective way to evolve your API without breaking changes is to follow effective API change management principles.
- For most APIs, versioning in the URI path is the most straightforward solution.
- For more complex or volatile APIs, you can manage varying scopes of changes by employing an integration of URI path and query params approaches.
Although these principles should provide clear direction in how to effectively manage change to your APIs, evolving an API is potentially more of an art than a science. It requires thought and foresight to create and maintain a reliable API.