API de versiones

9

Suponga que tiene un gran proyecto respaldado por una base API. El proyecto también incluye una API pública que los usuarios finales (ish) pueden usar.

En ocasiones, debe realizar cambios en la base de API que admite su proyecto. Por ejemplo, debe agregar una función que necesite un cambio de API, un nuevo método o requiera la alteración de uno de los objetos, o el formato de uno de esos objetos, pasado ao desde la API.

Suponiendo que también esté utilizando estos objetos en su API pública, los objetos públicos también cambiarán cada vez que lo haga, lo que es indeseable ya que sus clientes pueden confiar en que los objetos API permanecen idénticos para que su código de análisis funcione. (tos C ++ clientes WSDL ...)

Entonces, una posible solución es versionar la API. Pero cuando decimos "versionar" la API, parece que esto también debe significar versionar los objetos de la API, así como proporcionar llamadas a métodos duplicados para cada firma de método modificada. Entonces tendría un objeto clr viejo y simple para cada versión de mi api, lo que de nuevo parece indeseable. E incluso si hago esto, seguramente no construiré cada objeto desde cero, ya que eso terminaría con grandes cantidades de código duplicado. Más bien, es probable que la API extienda los objetos privados que estamos usando para nuestra API base, pero luego nos encontramos con el mismo problema porque las propiedades agregadas también estarían disponibles en la API pública cuando no se supone que lo estén.

Entonces, ¿qué es la cordura que generalmente se aplica a esta situación? Sé que muchos servicios públicos, como Git para Windows, mantienen una API versionada, pero tengo problemas para imaginar una arquitectura que soporte esto sin grandes cantidades de código duplicado que cubra los diversos métodos versionados y objetos de entrada / salida.

Soy consciente de que procesos como el control de versiones semántico intentan poner algo de cordura cuando se producen interrupciones de API públicas. El problema es más que parece que muchos o la mayoría de los cambios requieren romper la API pública si los objetos no están más separados, pero no veo una buena manera de hacerlo sin duplicar el código.

Caso
fuente
1
I don't see a good way to do that without duplicating code- Su nueva API siempre puede llamar a métodos en su antigua API, o viceversa.
Robert Harvey
2
AutoMapper al rescate, desafortunadamente sí, necesita versiones distintas de cada contrato, no olvide que todos los objetos a los que hace referencia su contrato son parte de ese contrato. El resultado es que su implementación real debe tener su propia versión única de modelos, y debe transformar la versión única en varias versiones de contrato. AutoMapper puede ayudarlo aquí, así como hacer que los modelos internos sean más inteligentes que los modelos contract. En ausencia de AutoMapper, he usado métodos de extensión para crear traducciones simples entre modelos internos y modelos de contrato.
Jimmy Hoffa
¿Existe una plataforma / contexto específico para esto? (es decir, DLL, una API REST, etc.)
GrandmasterB
.NET, con MVC y Webforms ui, clases dll. Tenemos un API de descanso y jabón.
Caso del

Respuestas:

6

Al mantener una API utilizada por terceros, es inevitable que deba realizar cambios. El nivel de complejidad dependerá del tipo de cambio que se esté produciendo. Estos son los principales escenarios que surgen:

  1. Nueva funcionalidad agregada a la API existente
  2. Funcionalidad antigua obsoleta de API
  3. Funcionalidad existente en API cambiando de alguna manera

Nueva funcionalidad que se agrega a la API existente

Este es el escenario más fácil de soportar. Agregar nuevos métodos a una API no debería requerir ningún cambio en los clientes existentes. Es seguro implementarlo para los clientes que necesitan la nueva funcionalidad, ya que no tiene actualizaciones para ningún cliente existente.

Funcionalidad antigua obsoleta de API

En este escenario, debe comunicar a los consumidores existentes de su API que la funcionalidad no será compatible a largo plazo. Hasta que deje de admitir la funcionalidad anterior (o hasta que todos los clientes hayan actualizado a la nueva funcionalidad), debe mantener la funcionalidad de API antigua y nueva al mismo tiempo. Si se trata de una biblioteca que se proporciona, la mayoría de los idiomas tienen una forma de marcar los métodos antiguos como obsoletos / obsoletos. Si se trata de un servicio de terceros de algún tipo, generalmente es mejor tener diferentes puntos finales para la funcionalidad antigua / nueva.

Funcionalidad existente en API cambiando de alguna manera

Este escenario dependerá del tipo de cambio. Si ya no es necesario utilizar los parámetros de entrada, puede actualizar el servicio / biblioteca para ignorar los datos adicionales ahora. En una biblioteca sería hacer que el método sobrecargado llame internamente al nuevo método que requiere menos parámetros. En un servicio alojado, el punto final ignora los datos adicionales y puede dar servicio a ambos tipos de clientes y ejecutar la misma lógica.

Si la funcionalidad existente necesita agregar nuevos elementos requeridos, entonces debe tener dos puntos finales / métodos para su servicio / biblioteca. Hasta que los clientes se actualicen, debe admitir ambas versiones.

otros pensamientos

Rather, the API is likely to extend the private objects we are using for our base API, but then we run into the same problem because added properties would also be available in the public API when they are not supposed to be.

No exponga objetos privados internos a través de su biblioteca / servicio. Cree sus propios tipos y asigne la implementación interna. Esto le permitirá realizar cambios internos y minimizar la cantidad de actualizaciones que los clientes externos deben realizar.

The problem is more that it seems like many or most changes require breaking the public API if the objects aren't more separated, but I don't see a good way to do that without duplicating code.

La API, ya sea un servicio o una biblioteca, debe ser estable en el punto de integración con los clientes. Cuanto más tiempo tome para identificar cuáles deberían ser las entradas y salidas y mantenerlas como entidades separadas, le ahorrará muchos dolores de cabeza en el futuro. Haga que el contrato de API sea su propia entidad separada y asigne las clases que proporcionan el trabajo real. El tiempo ahorrado cuando cambian las implementaciones internas debería más que compensar el tiempo adicional que llevó definir la interfaz adicional.

No vea este paso como "código duplicado". Si bien son similares, son entidades separadas que vale la pena crear. Mientras que los cambios de API externos casi siempre requieren un cambio correspondiente a la implementación interna, los cambios de implementación internos no siempre deberían cambiar la API externa.

Ejemplo

Supongamos que está proporcionando una solución de procesamiento de pagos. Está utilizando PaymentProviderA para realizar transacciones con tarjeta de crédito. Más adelante, obtendrá una mejor tarifa a través del procesador de pagos de PaymentProviderB. Si su API expuso los campos de Tarjeta de crédito / Dirección de factura de su tipo en lugar de la representación de PaymentProviderA, entonces el cambio de API es 0 ya que la interfaz sigue siendo la misma (con suerte, de todos modos, si PaymentProviderB requiere datos que no fueron requeridos por PaymentProviderA, entonces debe elegir admite ambos o mantén la peor tasa con PaymentProviderA).

Phil Patterson
fuente
Gracias por esta respuesta muy detallada. ¿Conoces algunos ejemplos de proyectos de código abierto que pueda leer para saber cómo los proyectos existentes han hecho esto? Me gustaría ver algunos ejemplos concretos de cómo han organizado el código que les permite hacer esto sin simplemente copiar sus diversos códigos de construcción de POCO en varios objetos de versión, ya que si llama a métodos compartidos, tendrá que dividirlo del método compartido para poder editar ese objeto para el objeto versionado.
Caso del
1
No conozco ningún buen ejemplo de mi cabeza. Si tengo algo de tiempo este fin de semana, podría crear una aplicación de prueba para lanzar en GitHub para mostrar cómo se podrían implementar algunas de estas cosas.
Phil Patterson
1
La parte difícil es que desde un alto nivel hay muchas maneras diferentes de tratar de minimizar el acoplamiento entre clientes y servidores. WCF tiene una interfaz llamada IExtensibleDataObject que le permite pasar datos que no están en el contrato del cliente y enviarlos por cable al servidor. Google creó Protobuf para la comunicación entre sistemas (existen implementaciones de código abierto para .NET, Java, etc.). Además, hay muchos sistemas basados ​​en mensajes que también pueden funcionar (suponiendo que su proceso se pueda realizar de forma asincrónica).
Phil Patterson
Puede que no sea una mala idea mostrar un ejemplo específico de la duplicación de código que está tratando de eliminar (como una nueva pregunta de desbordamiento de pila) y ver qué soluciones se le ocurren a la comunidad. Es difícil responder la pregunta en términos generales; entonces un escenario específico podría ser mejor.
Phil Patterson