¿Cuál es un patrón recomendado para la planificación de puntos finales REST para cambios previstos?

25

Intentar diseñar una API para aplicaciones externas con previsión para el cambio no es fácil, pero pensar un poco por adelantado puede facilitar la vida más adelante. Estoy tratando de establecer un esquema que admita cambios futuros sin dejar de ser compatible con versiones anteriores al dejar en su lugar controladores de versiones anteriores.

La principal preocupación en este artículo es qué patrón se debe seguir para todos los puntos finales definidos para un producto / empresa dado.

Esquema base

Dada una plantilla de URL base de https://rest.product.com/He ideado que todos los servicios residen /apijunto con /authy otros puntos finales que no se basan en el descanso, como /doc. Por lo tanto, puedo establecer los puntos finales básicos de la siguiente manera:

https://rest.product.com/api/...
https://rest.product.com/auth/login
https://rest.product.com/auth/logout
https://rest.product.com/doc/...

Puntos finales de servicio

Ahora para los puntos finales mismos. La preocupación por POST, GET, DELETEno es el objetivo principal de este artículo y es la preocupación de los propios actos.

Los puntos finales se pueden dividir en espacios de nombres y acciones. Cada acción también debe presentarse de una manera que admita cambios fundamentales en el tipo de retorno o los parámetros requeridos.

Al tomar un servicio de chat hipotético donde los usuarios registrados pueden enviar mensajes, podemos tener los siguientes puntos finales:

https://rest.product.com/api/messages/list/{user}
https://rest.product.com/api/messages/send

Ahora para agregar soporte de versión para futuros cambios de API que pueden estar rompiendo. Podríamos agregar la firma de la versión después /api/o después /messages/. Dado el sendpunto final, podríamos tener lo siguiente para v1.

https://rest.product.com/api/v1/messages/send
https://rest.product.com/api/messages/v1/send

Entonces, mi primera pregunta es, ¿cuál es un lugar recomendado para el identificador de versión?

Código de controlador de gestión

Por lo tanto, ahora hemos establecido que necesitamos admitir versiones anteriores, por lo que debemos manejar de alguna manera el código para cada una de las nuevas versiones que pueden dejar de funcionar con el tiempo. Suponiendo que estamos escribiendo puntos finales en Java, podríamos gestionar esto a través de paquetes.

package com.product.messages.v1;
public interface MessageController {
    void send();
    Message[] list();
}

Esto tiene la ventaja de que todo el código se ha separado mediante espacios de nombres donde cualquier cambio importante significaría una nueva copia de los puntos finales del servicio. El detrimento de esto es que todo el código debe copiarse y las correcciones de errores desean aplicarse a las versiones nuevas y anteriores, y deben aplicarse / probarse para cada copia.

Otro enfoque es crear controladores para cada punto final.

package com.product.messages;
public class MessageServiceImpl {
    public void send(String version) {
        getMessageSender(version).send();
    }
    // Assume we have a List of senders in order of newest to oldest.
    private MessageSender getMessageSender(String version) {
        for (MessageSender s : senders) {
            if (s.supportsVersion(version)) {
                return s;
            }
        }
    }
}

Esto ahora aísla el control de versiones de cada punto final y hace que las correcciones de errores sean compatibles con el puerto posterior, ya que en la mayoría de los casos solo se deben aplicar una vez, pero significa que debemos hacer un poco más de trabajo en cada punto final individual para admitir esto.

Entonces está mi segunda pregunta "¿Cuál es la mejor manera de diseñar el código de servicio REST para admitir versiones anteriores".

Brett Ryan
fuente

Respuestas:

13

Entonces está mi segunda pregunta "¿Cuál es la mejor manera de diseñar el código de servicio REST para admitir versiones anteriores".

Una API ortogonal y muy cuidadosamente diseñada probablemente nunca necesite cambiarse de manera incompatible con versiones anteriores, por lo que realmente la mejor manera es no tener versiones futuras.

Por supuesto, probablemente no lo consigas en el primer intento; Asi que:

  • Versión de su API, tal como está planeando (y es la API la que está versionada, no métodos individuales dentro), y haga mucho ruido al respecto. Asegúrese de que sus socios sepan que la API puede cambiar y que sus aplicaciones deben verificar si están utilizando la última versión; y aconsejar a los usuarios que actualicen cuando haya una nueva disponible. Respaldar dos versiones antiguas es difícil, soportar cinco es insostenible.
  • Resista el impulso de actualizar la versión API con cada "lanzamiento". Las nuevas características generalmente se pueden incluir en la versión actual sin romper los clientes existentes; Son nuevas características. Lo último que desea es que los clientes ignoren el número de versión, ya que de todos modos es principalmente compatible con versiones anteriores. Solo actualice la versión de la API cuando no pueda avanzar sin romper la API existente.
  • Cuando llega el momento de crear una nueva versión, el primer cliente debe ser la implementación compatible con versiones anteriores de la versión anterior. La "API de mantenimiento" debería implementarse en la API actual. De esta manera no estás enganchado para mantener varias implementaciones completas; solo la versión actual, y varios "shells" para las versiones anteriores. Ejecutar una prueba de regresión para la API ahora obsoleta contra el cliente retrocompatible es una buena manera de probar tanto la nueva API como la capa de compatibilidad.
SingleNegationElimination
fuente
3

La primera opción de diseño de URI expresa mejor la idea de que está versionando toda la API. El segundo podría interpretarse como versionar los mensajes mismos. Entonces esto es mejor IMO:

rest.product.com/api/v1/messages/send

Para la biblioteca cliente, creo que usar dos implementaciones completas en paquetes Java separados es más limpio, más fácil de usar y más fácil de mantener.

Dicho esto, hay mejores métodos para evolucionar las API que las versiones. Estoy de acuerdo con usted en que debe estar preparado para ello, pero creo que el control de versiones es el método de último recurso, para ser utilizado con cuidado y moderación. Es un esfuerzo relativamente grande para los clientes actualizar. Hace un tiempo he puesto algunos de estos pensamientos en una publicación de blog:

http://theamiableapi.com/2011/10/18/api-design-best-practice-plan-for-evolution/

Para las versiones de API REST específicamente, encontrará útil esta publicación de Mark Nottingham:

http://www.mnot.net/blog/2011/10/25/web_api_versioning_smackdown

Ferenc Mihaly
fuente
3

Otro enfoque para manejar el control de versiones de la API es usar Versión en Encabezados HTTP. Me gusta

POST /messages/list/{user} HTTP/1.1
Host: http://rest.service.com
Content-Type: application/json
API-Version: 1.0      <----- like here
Cache-Control: no-cache

Puede analizar el encabezado y manejarlo adecuadamente en el backend.

Con este enfoque, los clientes no necesitan cambiar la URL, solo el encabezado. Y también esto hace que los puntos finales REST sean más limpios, siempre.

Si alguno de los clientes no envió el encabezado de la versión, puede enviar 400 - Solicitud incorrecta o puede manejarlo con una versión de su API compatible con versiones anteriores .

sincerekamal
fuente