¡Cambios Drásticos Malos! ¡Versionado de API Bueno!
Como cualquiera que haya construido o use regularmente una API se da cuenta, tarde o temprano, los cambios drásticos son muy malos y pueden ser una mancha muy seria en una API que de otra manera sería útil. Un cambio drástico es un cambio en el comportamiento de una API que puede romper la integración de un usuario y resultar en mucha frustración y pérdida de confianza entre el proveedor de la API y el usuario. Los cambios drásticos requieren que los usuarios sean notificados con antelación (con las correspondientes disculpas) en lugar de un cambio que simplemente aparece, como una nueva característica encantadora. La manera de evitar esa frustración es versionar una API con garantías del dueño de la API de que no habrá cambios inesperados introducidos dentro de ninguna versión en particular.
¿Entonces, qué tan difícil puede ser versionar una API? La verdad es que no es difícil, pero lo que es complicado es mantener cierta cordura al no caer innecesariamente en un número desconcertante de versiones y subversiones aplicadas a docenas de endpoints de API con compatibilidades poco claras.
Introdujimos la v1 de la API hace tres años y no nos dimos cuenta de que seguiría funcionando hasta el día de hoy. ¿Cómo hemos continuado proporcionando la mejor API de entrega de correos electrónicos durante más de dos años pero aún mantenemos la misma versión de API? Aunque hay muchas opiniones diferentes sobre cómo versionar las APIs REST, espero que la historia de nuestra humilde pero poderosa v1 pueda guiarte en tu camino hacia la iluminación de versionado de APIs.
REST es Mejor
La API de SparkPost se origina de cuando éramos Message Systems, antes de nuestras aventuras en la nube. En ese momento estábamos ocupados haciendo los preparativos finales para el lanzamiento beta de Momentum 4. Esta era una actualización importante a la versión 3.x, nuestro MTA líder del mercado en las instalaciones. Momentum 4 incluía una interfaz de usuario completamente nueva, análisis en tiempo real, y lo más importante, una nueva API web para la inyección y generación de mensajes, gestión de plantillas y obtención de métricas de correo electrónico. Nuestra visión era de una arquitectura centrada en la API – donde incluso la interfaz de usuario interactuaría con los endpoints de la API.
Una de las decisiones más tempranas y mejores que tomamos fue adoptar un estilo RESTful. Desde finales de la década de 2000, la transferencia de estado representacional (REST) basada en APIs web son el estándar de facto de las APIs en la nube. Usar HTTP y JSON facilita a los desarrolladores, sin importar qué lenguaje de programación utilicen – PHP, Ruby, y Java – integrar con nuestra API sin necesidad de conocer o preocuparse por nuestra tecnología subyacente.
Elegir usar la arquitectura RESTful fue fácil. Elegir una convención de versionado no fue tan fácil. Inicialmente, eludimos la cuestión del versionado no versionando la beta en absoluto. Sin embargo, dentro de un par de meses, la beta estaba en manos de algunos clientes y comenzamos a desarrollar nuestro servicio en la nube. Hora de versionar. Evaluamos dos convenciones de versionado. La primera fue poner el versionado directamente en la URI y la segunda fue usar un encabezado Accept. La primera opción es más explícita y menos complicada, lo que es más fácil para los desarrolladores. Dado que amamos a los desarrolladores, fue la elección lógica.
Gobernanza de API
Con una convención de versionado seleccionada tuvimos más preguntas. ¿Cuándo subiríamos la versión? ¿Qué es un cambio drástico? ¿Reversionaríamos toda la API o solo ciertos endpoints? En SparkPost, tenemos múltiples equipos trabajando en diferentes partes de nuestra API. Dentro de esos equipos, las personas trabajan en diferentes endpoints en diferentes momentos. Por lo tanto, es muy importante que nuestra API sea consistente en el uso de convenciones. Esto era más grande que el versionado.
Establecimos un grupo de gobernanza que incluye ingenieros representando a cada equipo, un miembro del equipo de Gestión de Producto y nuestro CTO. Este grupo es responsable de establecer, documentar y hacer cumplir nuestras convenciones de API en todos los equipos. Un canal de Slack de gobernanza de API también es útil para debates animados sobre el tema.
El grupo de gobernanza identificó varias formas en que los cambios pueden ser introducidos en la API que son beneficiosos para el usuario y no constituyen un cambio drástico. Estos incluyen:
Un nuevo recurso o endpoint de API
Un nuevo parámetro opcional
Un cambio en un endpoint de API no público
Una nueva clave opcional en el cuerpo del POST JSON
Una nueva clave devuelta en el cuerpo de respuesta JSON
Por el contrario, un cambio drástico incluía cualquier cosa que pudiera romper la integración de un usuario, como:
Un nuevo parámetro requerido
Una nueva clave requerida en los cuerpos POST
Eliminación de un endpoint existente
Eliminación de un método de solicitud de endpoint existente
Un comportamiento interno materialmente diferente de una llamada de API – como un cambio en el comportamiento por defecto.
El Gran 1.0
A medida que documentamos y discutimos estas convenciones, también llegamos a la conclusión de que era en el mejor interés de todos (¡incluido el nuestro!) evitar hacer cambios drásticos en la API, ya que gestionar múltiples versiones agrega bastante esfuerzo. Decidimos que había algunas cosas que debíamos arreglar con nuestra API antes de comprometernos a “v1”.
Enviar un correo electrónico simple requería demasiado esfuerzo. Para “mantener las cosas simples simples”, actualizamos el cuerpo del POST para asegurarnos de que se adaptaran tanto los casos de uso simples como complejos. El nuevo formato también era más preparado para el futuro. En segundo lugar, abordamos un problema con el endpoint de Métricas. Este endpoint usaba un parámetro “group_by” que cambiaría el formato del cuerpo de respuesta GET de manera que la primera clave sería el valor del parámetro de agrupamiento. Eso no parecía muy RESTful, así que rompimos cada agrupamiento en un endpoint separado. Finalmente, auditamos cada endpoint y realizamos cambios menores aquí y allá para asegurarnos de que cumplieran con los estándares.
Documentación Precisa
Es importante tener documentación precisa y utilizable de la API para evitar cambios drásticos, de tipo deliberado o no intencional. Decidimos utilizar un enfoque simple de documentación de API aprovechando un lenguaje Markdown llamado API Blueprint y administrar nuestra documentación en Github. Nuestra comunidad contribuye y mejora estas documentaciones de código abierto. También mantenemos un conjunto de documentación no pública en Github para APIs y endpoints internos.
Inicialmente, publicamos nuestra documentación en Apiary, una gran herramienta para prototipar y publicar documentos de la API. Sin embargo, incrustar Apiary en nuestra página web no funciona en dispositivos móviles, así que ahora usamos Jekyll para generar documentación estática en su lugar. Nuestra última documentación de API de SparkPost ahora carga rápidamente y funciona bien en dispositivos móviles, lo cual es importante para los desarrolladores que no siempre están sentados frente a su computadora.
Separando el Despliegue de la Liberación
Aprendimos desde el principio el valioso truco de separar un despliegue de una liberación. De esta manera, es posible desplegar cambios frecuentemente cuando están listos a través de entrega y despliegue continuos, pero no siempre anunciamos o documentamos públicamente al mismo tiempo. No es raro que despleguemos un nuevo endpoint de API o una mejora a un endpoint API existente y lo usemos desde dentro de la interfaz de usuario o con herramientas internas antes de documentarlo públicamente y soportarlo. De esa manera podemos hacer algunos ajustes para mejorar la usabilidad o la conformidad con los estándares sin preocuparnos por hacer un temido cambio drástico. Una vez que estamos satisfechos con el cambio, lo añadimos a nuestra documentación pública.
¡Doh!
Es justo admitir que ha habido momentos en los que no hemos cumplido con nuestros ideales de “sin cambios drásticos” y estos son dignos de aprender. En una ocasión decidimos que sería mejor para los usuarios si una cierta propiedad estuviera configurada por defecto en verdadero en lugar de falso. Después de desplegar el cambio, recibimos varias quejas de usuarios ya que el comportamiento había cambiado inesperadamente. Revertimos el cambio y añadimos una configuración a nivel de cuenta – un enfoque mucho más amigable para el usuario, sin duda.
Ocasionalmente, nos sentimos tentados a introducir cambios drásticos como resultado de correcciones de errores. Sin embargo, decidimos dejar estas idiosincrasias solas en lugar de arriesgarnos a romper las integraciones de los clientes por el bien de la consistencia.
Hay casos raros en los que tomamos la seria decisión de hacer un cambio drástico – como descontinuar un recurso o método de API – en interés de la mayor comunidad de usuarios y solo después de confirmar que hay poco o ningún impacto para los usuarios. Por ejemplo, tomamos deliberadamente la decisión de alterar el comportamiento de respuesta de la API de Supresión pero solo después de sopesar cuidadosamente los beneficios y los impactos en la comunidad y comunicar cuidadosamente el cambio a nuestros usuarios. Sin embargo, nunca introduciríamos un cambio que tenga una remota posibilidad de impactar directamente en el envío de un correo electrónico de producción de un usuario.