Cómo versionar los URI REST

111

¿Cuál es la mejor manera de versionar los URI REST? Actualmente tenemos una versión # en la propia URI, es decir.

http://example.com/users/v4/1234/

para la versión 4 de esta representación.

¿La versión pertenece a queryString? es decir.

http://example.com/users/1234?version=4

¿O el control de versiones se logra mejor de otra manera?

Mike Pone
fuente

Respuestas:

34

Yo diría que hacerlo parte del URI en sí (opción 1) es mejor porque v4 identifica un recurso diferente al de v3. Los parámetros de consulta como en su segunda opción se pueden utilizar mejor para pasar información adicional (consulta) relacionada con la solicitud , en lugar del recurso .

Zef Hemel
fuente
11
La pregunta es, ¿es un RECURSO diferente el que estamos discutiendo? ¿O una representación diferente de ese recurso? ¿REST hace una distinción entre la representación y el recurso?
Cheeso
1
@Cheeso: el OP indica que es una representación diferente en lugar de un recurso diferente, de ahí mi respuesta.
Greg Beech
Esto ha sido respondido con mayor detalle antes aquí stackoverflow.com/q/389169/104261
Taras Alenin
+1 para "Los parámetros de consulta como en la segunda opción se pueden utilizar mejor para pasar información adicional (consulta) relacionada con la solicitud, en lugar del recurso"
andy
Para diferentes representaciones, creo que debe usar encabezados como "Aceptar", luego el cliente puede especificar al servidor "Acepto solo la versión 4" y el servidor puede responder con esa representación. Si no se envía ninguna aceptación, se proporciona la última versión.
Carlos Verdes
190

No versione las URL, porque ...

  • rompes enlaces permanentes
  • Los cambios de URL se propagarán como una enfermedad a través de su interfaz. ¿Qué haces con las representaciones que no han cambiado pero apuntan a la representación que tiene? Si cambia la URL, rompe los clientes antiguos. Si deja la URL, es posible que sus nuevos clientes no funcionen.
  • El control de versiones de los tipos de medios es una solución mucho más flexible.

Suponiendo que su recurso está devolviendo alguna variante de application / vnd.yourcompany.user + xml, todo lo que necesita hacer es crear soporte para un nuevo tipo de medio application / vnd.yourcompany.userV2 + xml y a través de la magia de la negociación de contenido su v1 y Los clientes v2 pueden coexistir pacíficamente.

En una interfaz RESTful, lo más parecido a un contrato es la definición de los tipos de medios que se intercambian entre el cliente y el servidor.

Las URL que utiliza el cliente para interactuar con el servidor deben ser proporcionadas por el servidor incrustado en representaciones recuperadas previamente. La única URL que debe conocer el cliente es la URL raíz de la interfaz. Agregar números de versión a las URL solo tiene valor si construye URL en el cliente, lo que se supone que no debe hacer con una interfaz RESTful.

Si necesita hacer un cambio en sus tipos de medios que romperá a sus clientes existentes, entonces cree uno nuevo y deje sus URL en paz.

Y para aquellos lectores que actualmente dicen que esto no tiene sentido si estoy usando application / xml y application / json como tipos de medios. ¿Cómo se supone que vamos a versionarlos? Tu no eres. Esos tipos de medios son bastante inútiles para una interfaz RESTful a menos que los analice mediante la descarga de código, en cuyo punto el control de versiones es un punto discutible.

Darrel Miller
fuente
66
Para abordar las viñetas. 1. No rompes los enlaces permanentes, porque los enlaces permanentes enlazan a una versión específica. 2. Si todo está versionado, esto no es un problema. Las URL antiguas aún pueden funcionar. Idealmente, no querrá que una URL de la versión 4 devuelva una asociación a un recurso de la versión 3. 3. Quizás
Mike Pone
10
¡Imagínese si cuando actualizara a una nueva versión de un navegador web, todos sus favoritos marcados se rompieran! Recuerde que, conceptualmente, el usuario guarda un enlace a un recurso, no a una versión de una representación de un recurso.
Darrel Miller
11
@Gili Para satisfacer el requisito de que una API REST sea autodescriptiva, es necesario que el encabezado de tipo de contenido proporcione la descripción semántica completa del mensaje. En otras palabras, su tipo de medio es su contrato de datos. Si entrega application / xml o application / json, no le está diciendo nada al cliente sobre lo que contiene ese XML / Json. En el instante en que una aplicación de cliente llega en una extracción / Cliente / Nombre, está creando un acoplamiento que se basa en información que no está en el mensaje. Eliminar el acoplamiento fuera de banda es fundamental para lograr RESTfulness.
Darrel Miller
6
@Gili El cliente no debe tener conocimiento previo de las URL de la API que no sean la URL raíz. No debe vincular formatos de representación a URL específicas. Cuando se trata de elegir tipos de medios, realmente necesita elegir entre un formato específico como application / vnd.mycompany.myformat + xml o uno estandarizado como, XHtml, Atom, RDF, etc.
Darrel Miller
4
¿Tiene sentido poner la versión de la API en un campo de encabezado separado? Así: Aceptar: application / com.example.myapp + json; versión = 1.0
Erik
21

Ah, me estoy poniendo mi viejo sombrero gruñón de nuevo.

Desde la perspectiva de ReST, no importa en absoluto. No es una salchicha.

El cliente recibe un URI que quiere seguir y lo trata como una cadena opaca. Ponga lo que quiera en él, el cliente no tiene conocimiento de algo como un identificador de versión en él.

Lo que el cliente sabe es que puede procesar el tipo de medio y le aconsejaré seguir el consejo de Darrel. También, personalmente, creo que la necesidad de cambiar 4 veces el formato utilizado en una arquitectura tranquila debería traer enormes señales de advertencia de que estás haciendo algo seriamente mal y evitar por completo la necesidad de diseñar tu tipo de medio para resistir al cambio.

Pero de cualquier manera, el cliente solo puede procesar un documento con un formato que pueda entender y seguir los enlaces que contiene. Debe conocer las relaciones de enlace (las transiciones). Entonces, lo que hay en el URI es completamente irrelevante.

Yo personalmente votaría por http: // localhost / 3f3405d5-5984-4683-bf26-aca186d21c04

Un identificador perfectamente válido que evitará que cualquier otro desarrollador de cliente o persona que toque el sistema se pregunte si se debe poner v4 al principio o al final de un URI (y sugiero que, desde la perspectiva del servidor, no debería tener 4 versiones, pero 4 tipos de medios).

SerialSeb
fuente
¿Qué pasa si la representación necesita cambiar significativamente y no será compatible con versiones anteriores?
Mike Pone
1
Al diseñar su tipo de medio de manera extensible, como mediante el uso de espacios de nombres y un xsd extensible, o formatos xml existentes ike atom, esto debería poder evitarse. Si realmente es necesario, otro tipo de medio es el camino a seguir.
SerialSeb
1
Me gusta esta respuesta completamente válida, pero creo que el URI propuesto es más para demostrar el punto que para un escenario real en el que desea URI 'pirateados'.
Dave Van den Eynde
10

NO debe poner la versión en la URL, debe poner la versión en el Encabezado de aceptación de la solicitud; vea mi publicación en este hilo:

¿Mejores prácticas para el control de versiones de API?

Si comienza a pegar versiones en la URL, termina con URL tontas como esta: http://company.com/api/v3.0/customer/123/v2.0/orders/4321/

Y hay un montón de otros problemas que también se infiltran; consulte mi blog: http://thereisnorightway.blogspot.com/2011/02/versioning-and-types-in-resthttp-api.html

Jeremyh
fuente
11
Lo siento, pero no creo que termines con URL tontas como esta. Está vinculando números de versión a un recurso en particular o (peor) a una representación en particular. Eso sería una tontería, en mi opinión. Más bien, está versionando la API, por lo que nunca tendrá más de una versión en el URI.
fool4jesus
3

Hay 4 enfoques diferentes para el control de versiones de la API:

  • Añadiendo versión a la ruta de URI:

    http://example.com/api/v1/foo
    
    http://example.com/api/v2/foo
    

    Cuando tenga un cambio importante, debe incrementar la versión como: v1, v2, v3 ...

    Puede implementar un controlador en su código como este:

    @RestController
    public class FooVersioningController {
    
    @GetMapping("v1/foo")
    public FooV1 fooV1() {
        return new FooV1("firstname lastname");
    }
    
    @GetMapping("v2/foo")
    public FooV2 fooV2() {
        return new FooV2(new Name("firstname", "lastname"));
    }
    
  • Solicitar control de versiones de parámetros:

    http://example.com/api/v2/foo/param?version=1
    http://example.com/api/v2/foo/param?version=2
    

    El parámetro de versión puede ser opcional o obligatorio según cómo desee que se utilice la API.

    La implementación puede ser similar a esta:

    @GetMapping(value = "/foo/param", params = "version=1")
    public FooV1 paramV1() {
        return new FooV1("firstname lastname");
    }
    
    @GetMapping(value = "/foo/param", params = "version=2")
    public FooV2 paramV2() {
        return new FooV2(new Name("firstname", "lastname"));
    }
    
  • Pasando un encabezado personalizado:

    http://localhost:8080/foo/produces
    

    Con encabezado:

    headers[Accept=application/vnd.company.app-v1+json]
    

    o:

    headers[Accept=application/vnd.company.app-v2+json]
    

    La mayor ventaja de este esquema es principalmente la semántica: no está saturando el URI con nada que ver con el control de versiones.

    Posible implementación:

    @GetMapping(value = "/foo/produces", produces = "application/vnd.company.app-v1+json")
    public FooV1 producesV1() {
        return new FooV1("firstname lastname");
    }
    
    @GetMapping(value = "/foo/produces", produces = "application/vnd.company.app-v2+json")
    public FooV2 producesV2() {
        return new FooV2(new Name("firstname", "lastname"));
    }
    
  • Cambiar nombres de host o usar puertas de enlace API:

    Básicamente, está moviendo la API de un nombre de host a otro. Incluso podría llamar a esta construcción una nueva API para los mismos recursos.

    Además, puede hacer esto usando API Gateways.

Javier C.
fuente
2

Si los servicios REST requieren autenticación antes de su uso, puede asociar fácilmente la clave / token de API con una versión de API y realizar el enrutamiento internamente. Para usar una nueva versión de la API, se podría requerir una nueva clave de API, vinculada a esa versión.

Desafortunadamente, esta solución solo funciona para API basadas en autenticación. Sin embargo, mantiene las versiones fuera de los URI.

UberSteve
fuente
1

Incluiría la versión como un valor opcional al final del URI. Esto podría ser un sufijo como / V4 o un parámetro de consulta como lo ha descrito. Incluso puede redirigir el / V4 al parámetro de consulta para admitir ambas variaciones.

Paul Morgan
fuente
1

Si usa URI para el control de versiones, entonces el número de versión debe estar en el URI de la raíz de la API, por lo que cada identificador de recurso puede incluirlo.

Técnicamente, una API REST no se rompe por cambios de URL (el resultado de la restricción de interfaz uniforme). Se interrumpe solo cuando la semántica relacionada (por ejemplo, un vocabulario RDF específico de la API) cambia de una manera no compatible con versiones anteriores (poco común). Actualmente, muchas personas no usan enlaces para la navegación (restricción HATEOAS) y vocabulario para anotar sus respuestas REST (restricción de mensaje autodescriptivo), por eso sus clientes rompen.

Los tipos MIME personalizados y el control de versiones de tipos MIME no ayudan, porque poner los metadatos relacionados y la estructura de la representación en una cadena corta no funciona. De c. los metadatos y la estructura cambiarán con frecuencia, por lo que el número de versión también ...

Entonces, para responder a su pregunta, la mejor manera de anotar sus solicitudes y respuestas con vocabulario ( Hydra , datos vinculados ) y olvidar el control de versiones o usarlo solo mediante cambios de vocabulario no compatibles con versiones anteriores (por ejemplo, si desea reemplazar un vocabulario con otro).

inf3rno
fuente
0

Voto para hacer esto en tipo mime pero no en URL. Pero la razón no es la misma que la de otros chicos.

Creo que la URL debería ser única (excepto esas redirecciones) para localizar el recurso único. Entonces, si acepta /v2.0en URL, ¿por qué no es /ver2.0o /v2/o /v2.0.0? ¿O incluso -alphay -beta? (entonces se convierte totalmente en el concepto de semver )

Entonces, la versión en tipo mime es más aceptable que la URL.

Yarco
fuente