A todos nos encanta tener herramientas nuevas y brillantes, pero odiamos la tarea de actualizarlas constantemente. Esto se aplica a cualquier cosa: sistemas operativos, aplicaciones, API, paquetes de Linux. Es doloroso cuando nuestro código deja de funcionar debido a una actualización y es el doble de doloroso cuando ni siquiera iniciamos la actualización.
En el desarrollo de API web, corres constantemente el riesgo de romper el código de tus usuarios con cada nueva actualización. Si su producto es una API, estas actualizaciones siempre serán aterradoras. Los principales productos de Monite son nuestra API y nuestro SDK de marca blanca. Somos una empresa que prioriza las API, por lo que nos preocupamos mucho de mantener nuestra API estable y fácil de usar. Por lo tanto, el problema de los cambios importantes está en la parte superior de nuestra lista de prioridades.
Una solución común es emitir advertencias de obsolescencia a sus clientes y publicar cambios importantes en raras ocasiones. De repente, sus lanzamientos pueden tardar meses y algunas funciones deben permanecer ocultas o incluso no fusionadas hasta cada próximo lanzamiento. Esto ralentiza su desarrollo y obliga a sus usuarios a actualizar su integración cada pocos meses.
Si realizas lanzamientos más rápido, tus usuarios tendrán que actualizar su integración con demasiada frecuencia. Si alargas el tiempo entre lanzamientos, avanzarás más lento como empresa. Cuanto más inconveniente lo haga para los usuarios, más conveniente será para usted y viceversa. Ciertamente este no es un escenario óptimo. Queríamos avanzar a nuestro propio ritmo sin alterar nada para los clientes existentes, lo que sería imposible con un enfoque de desaprobación regular. Por eso elegimos una solución alternativa: Control de versiones de API.
Es una idea bastante simple: publicar cualquier cambio importante en cualquier momento, pero ocultarlo en una nueva versión de API. Te otorga lo mejor de ambos mundos: las integraciones de los usuarios no se interrumpirán de forma rutinaria y podrás moverte a la velocidad que desees. Los usuarios migrarán cuando quieran, sin presión alguna.
Teniendo en cuenta la simplicidad de la idea, parece perfecta para cualquier empresa. Eso es lo que esperarías leer en un típico blog de ingeniería. Lamentablemente, no es tan sencillo.
El control de versiones de API es difícil, muy difícil. Su ilusoria simplicidad se desvanece rápidamente una vez que comienzas a implementarla. Lamentablemente, Internet nunca te advierte, ya que sorprendentemente hay pocos recursos sobre el tema. La mayoría absoluta de ellos discuten sobre dónde colocar la versión API, pero sólo unos pocos artículos escasos intentan responder: "¿Cómo implementamos?". Los más comunes son:
Las implementaciones separadas pueden resultar muy costosas y difíciles de soportar, copiar rutas individuales no se adapta muy bien a cambios grandes y copiar la aplicación completa crea tanto código adicional que comenzará a ahogarse en él después de unas pocas versiones.
Incluso si intentas elegir el más barato, la carga de las versiones te alcanzará pronto. Al principio, parecerá sencillo: agregue otro esquema aquí, otra rama de la lógica empresarial allá y duplique algunas rutas al final. Pero con suficientes versiones, su lógica de negocios rápidamente se volverá inmanejable, muchos de sus desarrolladores confundirán las versiones de la aplicación y las versiones de API, y comenzarán a versionar los datos dentro de su base de datos, y su aplicación será imposible de mantener.
Es posible que desees no tener nunca más de dos o tres versiones de API al mismo tiempo; que podrás eliminar versiones antiguas cada pocos meses. Es cierto si solo apoya a un pequeño número de consumidores internos. Pero los clientes fuera de su organización no disfrutarán la experiencia de verse obligados a actualizar cada pocos meses.
El control de versiones de API puede convertirse rápidamente en una de las partes más costosas de su infraestructura, por lo que es fundamental realizar una investigación diligente de antemano. Si solo brinda soporte a consumidores internos, es posible que le resulte más sencillo utilizar algo como GraphQL, pero rápidamente puede volverse tan costoso como el control de versiones.
Si es una startup, sería prudente posponer el control de versiones de la API hasta las últimas etapas de su desarrollo, cuando tenga los recursos para hacerlo correctamente. Hasta entonces, las desaprobaciones y la estrategia de cambio aditivo podrían ser suficientes. Su API no siempre se verá genial, pero al menos ahorrará una gran cantidad de dinero al evitar el control de versiones explícitas.
Después de algunas pruebas y muchos errores, nos encontramos en una encrucijada: nuestros enfoques de versiones anteriores que mencionamos anteriormente eran demasiado costosos de mantener. Como resultado de nuestras luchas, ideé la siguiente lista de requisitos que se requerirían de un marco de control de versiones perfecto:
Lamentablemente, había pocas o ninguna alternativa a nuestros enfoques existentes. Fue entonces cuando se me ocurrió una idea loca: ¿qué pasaría si intentáramos crear algo sofisticado, algo perfecto para el trabajo, algo como el control de versiones API de Stripe?
Como resultado de innumerables experimentos, ahora tenemos Cadwyn: un marco de control de versiones de API de código abierto que no solo implementa el enfoque de Stripe, sino que se basa significativamente en él. Hablaremos sobre su implementación Fastapi y Pydantic, pero los principios básicos son independientes del lenguaje y el marco.
El problema de todos los demás enfoques de control de versiones es que estamos duplicando demasiado. ¿Por qué duplicaríamos toda la ruta, el controlador o incluso la aplicación cuando solo se rompió una pequeña parte de nuestro contrato?
Con Cadwyn, cada vez que los mantenedores de API necesitan crear una nueva versión, aplican los cambios más importantes a sus esquemas, modelos y lógica de negocios más recientes. Luego crean un cambio de versión: una clase que encapsula todas las diferencias entre la nueva versión y una versión anterior.
Por ejemplo, digamos que anteriormente nuestros clientes podían crear un usuario con una dirección pero ahora nos gustaría permitirles especificar varias direcciones en lugar de una sola. El cambio de versión se vería así:
class ChangeUserAddressToAList(VersionChange): description = ( "Renamed `User.address` to `User.addresses` and " "changed its type to an array of strings" ) instructions_to_migrate_to_previous_version = ( schema(User).field("addresses").didnt_exist, schema(User).field("address").existed_as(type=str), ) @convert_request_to_next_version_for(UserCreateRequest) def change_address_to_multiple_items(request): request.body["addresses"] = [request.body.pop("address")] @convert_response_to_previous_version_for(UserResource) def change_addresses_to_single_item(response): response.body["address"] = response.body.pop("addresses")[0]Cadwyn utiliza
instructions_to_migrate_to_previous_version para generar código para versiones API anteriores de esquemas y las dos funciones de conversión son el truco que nos permite mantener tantas versiones como queramos. El proceso se parece al siguiente:
Después de que nuestros mantenedores de API hayan creado el cambio de versión, deben agregarlo a nuestro VersionBundle para decirle a Cadwyn que este VersionChange se incluirá en alguna versión:
VersionBundle( Version( date(2023, 4, 27), ChangeUserAddressToAList ), Version( date(2023, 4, 12), CollapseUserAvatarInfoIntoAnID, MakeUserSurnameRequired, ), Version(date(2023, 3, 15)), )
Eso es todo: hemos agregado un cambio importante, pero nuestra lógica de negocios solo maneja una única versión: la más reciente. Incluso después de agregar docenas de versiones de API, nuestra lógica de negocios seguirá estando libre de lógica de versiones, cambios de nombre constantes, if y convertidores de datos.
Los cambios de versión dependen de la interfaz pública de la API y casi nunca agregamos cambios importantes a las versiones de API existentes. Esto significa que una vez que hayamos lanzado la versión, no se romperá.
Debido a que los cambios de versión describen cambios importantes dentro de las versiones y no hay cambios importantes dentro de las versiones anteriores, podemos estar seguros de que nuestros cambios de versión son completamente inmutables: nunca tendrán una razón para cambiar. Las entidades inmutables son mucho más fáciles de mantener que si fueran parte de la lógica empresarial porque siempre está evolucionando. Los cambios de versión también se aplican uno tras otro, formando una cadena de transformadores entre versiones que pueden migrar cualquier solicitud a cualquier versión más nueva y cualquier respuesta a cualquier versión anterior.
Los contratos API son mucho más complejos que solo esquemas y campos. Consisten en todos los puntos finales, códigos de estado, errores, mensajes de error e incluso comportamientos de lógica empresarial. Cadwyn usa el mismo DSL que describimos anteriormente para manejar puntos finales y códigos de estado, pero los errores y los comportamientos de la lógica de negocios son una historia diferente: son imposibles de describir usando un DSL, deben integrarse en la lógica de negocios.
Esto hace que dichos cambios de versión sean mucho más costosos de mantener que todos los demás porque afectan la lógica empresarial. A esta propiedad la llamamos "efecto secundario" y tratamos de evitarlos a toda costa debido a su carga de mantenimiento. Todos los cambios de versión que quieran modificar la lógica empresarial deberán marcarse como si tuvieran efectos secundarios. Servirá como forma de saber qué cambios de versión son "peligrosos":
class RequireCompanyAttachedForPayment(VersionChangeWithSideEffects): description = ( "User must now have a company_id in their account " "if they want to make new payments" )
También permitirá a los mantenedores de API verificar que la solicitud del cliente utilice una versión de API que incluya este efecto secundario:
if RequireCompanyToBeAttachedForPayment.is_applied: validate_company_id_is_attached(user)
Cadwyn tiene muchos beneficios: reduce en gran medida la carga de nuestros desarrolladores y se puede integrar en nuestra infraestructura para generar automáticamente el registro de cambios y mejorar nuestros documentos API.
Sin embargo, la carga del control de versiones todavía existe e incluso un marco sofisticado no es una solución milagrosa. Hacemos todo lo posible para utilizar versiones de API solo cuando sea absolutamente necesario. También intentamos que nuestra API sea correcta en el primer intento mediante un "Consejo de API" especial. Todos los cambios importantes de API son revisados allí por nuestros mejores desarrolladores, evaluadores y redactores de tecnología antes de que cualquier implementación comience a funcionar.
Un agradecimiento especial a Brandur Leach por su artículo sobre versiones de API en Stripe y por la ayuda que me brindó cuando implementé Cadwyn: no sería posible sin su ayuda.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3