Compatibilidad

En esta página se proporcionan más explicaciones detalladas para la lista de cambios rotundos y no rotundos que se ofrecen en la sección Control de versiones.

No siempre es muy claro lo que cuenta como un cambio rotundo (incompatible). Esta guía debería tratarse como orientadora más que como una lista completa de todos los cambios posibles.

Las reglas que se enumeran aquí solo se refieren a la compatibilidad del cliente. Se espera que los productores de API conozcan sus propios requisitos con respecto a la implementación, incluidos los cambios en los detalles de implementación.

El objetivo general es que los clientes no experimenten fallas por una actualización del servicio a una versión o un parche nuevos secundarios. Los siguientes son los tipos de fallas que se consideran:

  • Compatibilidad de fuente: código escrito en 1.0 que no se compila en 1.1
  • Compatibilidad binaria: código compilado con 1.0 que no se vincula o se ejecuta en una biblioteca cliente 1.1 (los detalles exactos dependen de la plataforma del cliente; hay variantes de esto en diferentes situaciones)
  • Compatibilidad de conexión: una aplicación compilada en 1.0 que no se puede comunicar con un servidor 1.1
  • Compatibilidad semántica: todo se ejecuta, pero produce resultados inesperados o no deseados

En otras palabras: los clientes anteriores deberían poder funcionar en servidores más nuevos dentro del mismo número de versión principal y cuando quieran actualizarse a una versión secundaria nueva (por ejemplo, para aprovechar una característica nueva), deberían poder hacerlo con facilidad.

Nota: Cuando nos referimos a números de versión, como v1.1 y v1.0, nos referimos a números de versión lógicos que nunca son concretos. Su único propósito es facilitar la descripción de los cambios.

Además de las consideraciones teóricas y basadas en el protocolo, hay consideraciones prácticas debido a la existencia de bibliotecas cliente que involucran el código generado y el código escrito a mano. Siempre que sea posible, genera versiones nuevas de las bibliotecas cliente y asegúrate de que sus pruebas demuestran que los cambios que consideras son aceptables.

La discusión a continuación divide los proto mensajes en tres categorías:

  • Mensajes de solicitud (como GetBookRequest)
  • Mensajes de respuesta (como ListBooksResponse)
  • Mensajes de recurso (como Book y cualquier mensaje que se use dentro de otros mensajes de recursos).

Estas categorías tienen reglas diferentes, ya que los mensajes de solicitud solo se envían desde el cliente hasta el servidor, los mensajes de respuesta solo se envían desde el servidor al cliente, pero los mensajes de recurso se suelen enviar en ambos sentidos. En particular, los recursos que pueden actualizarse necesitan considerarse en términos de un ciclo de lectura, modificación y escritura.

Cambios compatibles con versiones anteriores (no rotundos)

Agrega una interfaz de API a una definición de servicio de API

Desde la perspectiva del protocolo, esto siempre es seguro. La única advertencia es que las bibliotecas cliente pueden haber usado el nombre nuevo de la interfaz de API mediante código escrito a mano. Si tu interfaz nueva es ortogonal por completo a las existentes, es poco probable que esto ocurra; si es una versión simplificada de una interfaz existente, entonces sí es más probable que haya un conflicto.

Agrega un método a una interfaz de API

A menos que agregues un método que entra en conflicto con otro que ya se genera en bibliotecas cliente, no debería haber inconvenientes.

(Ejemplo en el que podría estar fallando: si tienes un método GetFoo, el generador de código de C# ya creará los métodos GetFoo y GetFooAsync. Agregar un método GetFooAsync en tu interfaz de API sería, por lo tanto, un cambio rotundo desde la perspectiva de una biblioteca cliente.

Agrega una vinculación HTTP a un método

Si supones que la vinculación no conlleva ninguna ambigüedad, es seguro hacer que el servidor responda a una URL que antes habría rechazado. Esto puede hacerse cuando una operación existente se aplica a un patrón de nombre de recurso nuevo.

Agrega un campo a un mensaje de solicitud

Agregar campos de solicitud puede ser no rotundo mientras los clientes que no especifiquen el campo se traten de la misma manera en la versión nueva que en la anterior.

El ejemplo más obvio de dónde se puede hacer esto de manera incorrecta es con la paginación: si la v1.0 de la API no incluye la paginación de una colección, no se puede agregar a la v1.1 a menos que el valor predeterminado page_size se trate como infinito (lo cual suele ser una mala idea). De lo contrario, los clientes de la v1.0 que esperan obtener resultados completos de una sola solicitud podrían recibir resultados truncados, sin saber que la colección contiene más recursos.

Agrega un campo a un mensaje de respuesta

Un mensaje de respuesta que no es un recurso (p. ej., ListBooksResponse) puede expandirse sin romper clientes, siempre que esto no cambie el comportamiento de otros campos de respuesta. Cualquier campo que se propagó antes en una respuesta debe continuar con la misma semántica, incluso si esto genera redundancias.

Por ejemplo, una respuesta de consulta en 1.0 podría tener un campo booleano de contained_duplicates para indicar que algunos resultados se omitieron debido a la duplicación. En 1.1, es posible que proporcionemos información más detallada en un campo duplicate_count. Aunque es redundante desde la perspectiva de la 1.1, el campo contained_duplicates debe propagarse de todas formas.

Agrega un valor a una enumeración

Una enumeración que solo se usa en un mensaje de solicitud puede expandirse con libertad para incluir elementos nuevos. Por ejemplo, si se usa el patrón Vista de recursos, puede agregarse una vista nueva en una versión secundaria nueva. Los clientes nunca necesitan recibir esta enumeración, así que no tienen que saber los valores que no importan.

Para los mensajes de recursos y los de respuesta, la suposición predeterminada es que los clientes deben manejar los valores de enumeración que no conocen. Sin embargo, los productores de API deberían saber que puede ser difícil escribir aplicaciones para manejar elementos de enumeración nuevos de manera correcta. Los propietarios de API deberían documentar el comportamiento esperado del cliente cuando se encuentran con un valor de enumeración desconocido.

Proto3 permite a los clientes recibir un valor que desconocen y volver a serializar el mensaje mientras se mantiene el mismo valor, por lo que no se interrumpe el ciclo de escritura, modificación y escritura. El formato JSON permite enviar un valor numérico donde el “nombre” del valor es desconocido, pero el servidor por lo general no sabrá si el cliente conoce un valor en particular. Los clientes JSON pueden, por lo tanto, saber que recibieron un valor que antes era desconocido, pero solo verán el nombre o el número, no sabrán ambos. Mostrar el mismo valor al servidor en un ciclo de lectura, modificación y escritura no debe modificar ese campo, ya que el servidor debería entender ambas formas.

Agrega un campo de recursos de solo resultados

Pueden agregarse campos en una entidad de recursos que solo suministra el servidor. El servidor puede validar que cualquier valor proporcionado por el cliente en una solicitud es válido, pero no debe fallar si se omite el valor.

Cambios no compatibles con versiones anteriores (rotundos)

Quita o renombra un servicio, campo, método o valor de enumeración

De modo fundamental, si el código del cliente puede referirse a algo, entonces quitarlo o renombrarlo es un cambio rotundo que debe reflejarse en un aumento del número de versión principal. El código que se refiere al nombre anterior causará errores en el tiempo de compilación para algunos lenguajes (como C# y Java) y puede causar fallos en el tiempo de ejecución o pérdida de datos en otros lenguajes. La compatibilidad del formato de conexión no es relevante aquí.

Cambia una vinculación HTTP

“Cambiar” aquí se refiere a “borrar y agregar”. Por ejemplo, si decides que quieres admitir PATCH, pero tu versión publicada admite PUT o usaste el nombre de verbo personalizado incorrecto, puedes agregar la vinculación nueva, pero no debes quitar la vinculación anterior por las mismas razones que implican que quitar un método de servicio es un cambio rotundo.

Cambia el tipo de un campo

Incluso cuando el tipo nuevo es compatible con el formato de conexión, esto podría cambiar el código generado para las bibliotecas cliente y, por lo tanto, debe dar como resultado un aumento de versión principal. Para lenguajes compilados de tipo estático, esto puede generar con facilidad errores en el tiempo de compilación.

Cambia un formato de nombre de recurso

No se debe cambiar el nombre de un recurso, lo que significa que los nombres de colección no pueden cambiarse.

A diferencia de la mayoría de los cambios rotundos, esto afecta a las versiones principales también: si un cliente planea usar el acceso de v2.0 a un recurso creado en una v1.0 o vice versa, el mismo nombre de recurso debería usarse en ambas versiones.

En menor medida, el conjunto de nombres de recurso válidos tampoco debería cambiar, por las siguientes razones:

  • Si se vuelve más restrictivo, una solicitud que antes tenía éxito, ahora fallará.
  • Si se vuelve menos restrictivo que el que se documentó antes, los clientes que hacen suposiciones según la documentación previa pueden tener fallas. Es muy probable que los clientes almacenen nombres de recursos en otros lugares, de manera que pueden ser sensibles respecto al conjunto de caracteres permitidos y la longitud del nombre. De forma alternativa, los clientes pueden realizar su propia validación de nombres de recursos para seguir la documentación (por ejemplo, Amazon realizó muchas advertencias a sus clientes y tuvo un período de migración cuando comenzaron a permitir ID de recursos EC2 más largos).

Ten en cuenta que un cambio de este tipo solo puede ser visible en la documentación de un proto. Por lo tanto, cuando revises si una CL tiene errores, no es suficiente revisar los cambios que no son comentarios.

Cambia el comportamiento visible de las solicitudes existentes

Los clientes a menudo dependerán del comportamiento y la semántica de la API, incluso cuando ese comportamiento no se admite ni está documentado de manera explícita. Por lo tanto, en la mayoría de los casos, cambiar el comportamiento o la semántica de los datos de la API se verá como una falla por parte de los consumidores. Si el comportamiento no se oculta de manera criptográfica, deberías suponer que los usuarios lo descubrieron y que dependerán de él.

También es una buena idea encriptar los tokens de paginación por esta razón (incluso cuando los datos no sean interesantes), para prevenir que los usuarios creen sus propios tokens y que tengan la posibilidad de experimentar fallas cuando cambie el comportamiento del token.

Cambia el formato de URL en la definición HTTP

Hay dos tipos de cambios que deben considerarse aquí, más allá de los cambios de nombre de recurso enumerados antes:

  • Nombres de método personalizados: aunque no son parte del nombre del recurso, un nombre de método personalizado es parte de la URL a la que publican los clientes REST. Cambiar un nombre de método personalizado no debería causar fallas en los clientes gRPC, pero las API públicas deben suponer que tienen clientes REST.
  • Nombres de parámetros de recursos: Un cambio de v1/shelves/{shelf}/books/{book} a v1/shelves/{shelf_id}/books/{book_id} no afecta el nombre del recurso sustituido, pero puede afectar la generación de código.

Agrega un campo de lectura o escritura a un mensaje de recurso

Los clientes a menudo realizarán operaciones de lectura, modificación y escritura. La mayoría de los clientes no suministrarán valores para campos que no conocen y proto3 en particular no lo admite. Puedes especificar que cualquier campo faltante de tipos de mensajes (en lugar de tipos básicos) significa que una actualización no se aplica a esos campos, pero esto hace que sea más difícil quitar de manera explícita ese valor de campo de una entidad. Los tipos básicos (incluidos string y bytes) no pueden manejarse de esta manera, ya que no hay diferencias en proto3 entre especificar un campo int32 de manera explícita como 0 y no especificarlo.

Si todas las actualizaciones se realizan con una máscara de campo, esto no es un problema, ya que el cliente no reemplazará de manera implícita los campos que no conozca. Sin embargo, esa sería una decisión inusual de la API: la mayoría de las API permiten actualizaciones de “recursos completos”.