2011-02-07 5 views
10

La mejor práctica para el control de versiones de recurso REST es colocar la información de la versión en los encabezados Accept/Content-Type de la solicitud HTTP que deja el URI intacto.Versiones de recurso REST fácil en implementaciones basadas en JAX-RS?

Aquí está la muestra/respuesta a REST API para recuperar información del sistema: la atención

==> 
GET /api/system-info HTTP/1.1 
Accept: application/vnd.COMPANY.systeminfo-v1+json 

<== 
HTTP/1.1 200 OK 
Content-Type: application/vnd.COMPANY.systeminfo-v1+json 
{ 
    “session-count”: 19 
} 

de pago que se especifica en la versión tipo MIME.

Aquí es otra petición/respuesta para la versión 2:

==> 
GET /api/system-info HTTP/1.1 
Accept: application/vnd.COMPANY.systeminfo-v2+json 

<== 
HTTP/1.1 200 OK 
Content-Type: application/vnd.COMPANY.systeminfo-v2+json 
{ 
    “uptime”: 234564300, 
    “session-count”: 19 
} 

Ver http://barelyenough.org/blog/tag/rest-versioning/ para más explicación y ejemplos.

¿Es posible implementar este enfoque fácilmente en implementaciones basadas en Java JAX-RS, como Jersey o Apache CXF?

El objetivo es tener varias clases @Resource con el mismo valor de @Path, pero sirviendo la solicitud en función de la versión real especificada en el tipo MIME?

He investigado JAX-RS en general y Jersey in particlaur y no encontré soporte para eso. Jersey no da la oportunidad de registrar dos recursos con el mismo camino. La sustitución de la clase WebApplicationImpl debe implementarse para admitir eso.

¿Puede sugerir algo?

NOTA: Se requiere que las versiones múltiples del mismo recurso estén disponibles simultáneamente. Las nuevas versiones pueden introducir cambios incompatibles.

+2

Esto definitivamente NO es la mejor práctica para versionar una API. La mejor práctica es NO tener versiones y solo hacer cambios compatibles. La creación artificial de nuevos tipos MIME para los cambios que cada cliente sensato debería tratar de forma automática (agregar nuevas etiquetas/claves a sus datos) no es RESTful en absoluto en mi libro. –

+4

Bueno, NO siempre es posible hacer cambios compatibles. Además, en mi caso, múltiples versiones de recursos REST deben ser soportadas simultáneamente. En cuanto a la identidad de los recursos debe preservarse URI debe cambiar el mismo. La nueva versión es la nueva representación del recurso, es decir, un nuevo tipo MIME. –

+0

Gracias por su comentario, actualizaré la pregunta original para ser más específico –

Respuesta

6

JAX-RS se envía a métodos anotados con @Produces a través del encabezado Accept. Por lo tanto, si desea que JAX-RS realice su despacho, deberá aprovechar este mecanismo. Sin ningún trabajo adicional, tendría que crear un método (y Proveedor) para cada tipo de medio que desee.

No hay nada que te impida tener varios métodos basados ​​en el tipo de medio que todos llaman un método común para hacer ese trabajo, pero tendrías que actualizarlo y agregar código cada vez que agregaras un nuevo tipo de medio.

Una idea es agregar un filtro que "normalice" el encabezado Aceptar específicamente para el envío. Es decir, tal vez, tomando su:

Accept: application/vnd.COMPANY.systeminfo-v1+json 

y convertir que a, simplemente:

Accept: application/vnd.COMPANY.systeminfo+json 

Al mismo tiempo, extraer la información de versión para su uso posterior (tal vez en la solicitud, o alguna otro mecanismo ad hoc).

Luego, JAX-RS se enviará al único método que maneja "application/vnd.COMPANY.systeminfo + json".

ESE método luego toma la información de control de versiones "fuera de banda" para manejar los detalles en el procesamiento (como seleccionar la clase adecuada para cargar a través de OSGi).

A continuación, cree un Proveedor con un MessageBodyWriter apropiado. El proveedor será seleccionado por JAX-RS para la aplicación/vnd.COMPANY.systeminfo + json tipo de medio. Depende de su MBW averiguar el tipo de medio real (basado de nuevo en esa información de versión) y crear el formato de salida correcto (de nuevo, tal vez enviando a la clase correcta cargada OSGi).

No sé si un MBW puede sobreescribir el encabezado Content-Type o no. De lo contrario, puede delegar el filtro anterior para reescribir esa parte en el camino de salida.

Es un poco intrincado, pero si desea aprovechar el despacho de JAX-RS y no crear métodos para cada versión de su tipo de medio, entonces esta es una ruta posible para hacerlo.

Editar en respuesta al comentario:

Sí, en esencia, que desea JAX-RS para despachar a la clase apropiada basándose tanto en Path y aceptar tipo. Es poco probable que JAX-RS lo haga de la caja, ya que es un caso marginal. No he analizado ninguna de las implementaciones de JAX-RS, pero es posible que pueda hacer lo que quiera modificando uno de los niveles de infraestructura.

Posiblemente otra opción menos invasiva es utilizar un viejo truco del mundo de Apache, y simplemente crear un filtro que reescribe su ruta basada en el encabezado Aceptar.

Por lo tanto, cuando el sistema obtiene:

GET /resource 
Accept: application/vnd.COMPANY.systeminfo-v1+json 

reescribes a:

GET /resource-v1 
Accept: application/vnd.COMPANY.systeminfo-v1+json 

A continuación, en la clase de JAX-RS:

@Path("resource-v1") 
@Produces("application/vnd.COMPANY.systeminfo-v1+json") 
public class ResourceV1 { 
    ... 
} 

lo tanto, sus clientes obtener la vista correcta, pero JAX-RS envía sus clases correctamente. El único otro problema es que sus clases, si miran, verán la ruta modificada, no la ruta original (pero su filtro puede incluir eso en la solicitud como referencia si lo desea).

No es ideal, pero es (la mayoría) gratis.

This es un filtro existente que podría hacer lo que quiera hacer, si no puede servir de inspiración para que usted mismo lo haga.

+0

Muchas gracias por la respuesta. Esto se está acercando a lo que necesito. La transformación de MIME hará la parte del trabajo. Sin embargo, no quiero que el método de recursos se ocupe de la información de versiones. La clase de recurso completo debe representar la versión de recurso específica Y habrá varias clases de recursos en el tiempo de ejecución, p. RestService en el paquete 1.0 y RestService en el paquete 2.0, ambos con @Path ('/ rest'). Quiero instruir a Jersey para que diferencie entre los dos sin volver a escribir WebApplicationImpl si es posible. (si no es posible, me moveré y reescribiré/ampliaré) –

+0

Esta es probablemente la mejor respuesta hasta ahora. Idealmente, no tendré versiones en @Path, @Produces y nombres de clases de recursos para mi clase, ya que la versión debe tomarse del especificador de la versión del paquete OSGi. Pero de nuevo, ese es el escenario IDEAL. Le diste consejos muy útiles aquí. ¡Gracias! –

0

Una posible solución es utilizar uno @Path con

Content-Type: application/vnd.COMPANY.systeminfo- {version} + JSON

Entonces, dentro del método de la @Path dada, puede llamar a la versión de WebService

+0

El problema es que no quiero mover la lógica específica de la versión directamente dentro de la implementación del método. Quiero que eso se maneje afuera. –

+0

De todos modos, gracias por los comentarios :) –

+0

De cualquier manera tendrá que "crear una capa para elegir qué método llamar". La forma en que propone esta capa llamará a un método específico basado en el contenido de @Consumes. La solución que publiqué tendrá solo un @Consumes y elegirá el método para llamar dentro de él. Para mí es lo mismo, una forma de manejar anotaciones y replicar en métodos. De la otra manera, puedes replicar llamadas a métodos. –

0

Si está utilizando CXF, podría use the technique specified here crear un nuevo proveedor de serialización (construyendo desde la infraestructura existente) que produce los datos en el formato específico deseado. Declare un par de ellos, uno para cada formato específico que desee, y use la anotación @Produces para que la maquinaria maneje el resto de la negociación por usted, aunque también podría ser una idea admitir el tipo de contenido JSON estándar también para que los clientes normales pueden manejarlo sin necesidad de asimilar su especialidad.La única pregunta real entonces se convierte en cuál es la mejor manera de hacer la serialización; Supongo que usted puede darse cuenta de eso por sí mismo ...


[EDIT]: Además de excavación en el CXF documentation conduce a la revelación de que tanto los @Consumes y @Produces anotaciones son considerados como ejes para hacer la selección. Si desea tener dos métodos que manejen la producción de la respuesta para diferentes tipos de medios, sin duda puede. (Deberá agregar los proveedores de serialización y/o deserialización si usa tipos personalizados, pero puede hacer la delegación de la mayoría del trabajo a los proveedores estándar). Me gustaría advertirle que debe hacerlo. aún así garantizar que el recurso indicado por la ruta sea el mismo en ambos casos; hacer lo contrario no es RESTful.

+0

Donal, gracias por la respuesta. Has señalado la sugerencia al proveedor de serialización, que es parte de la preparación _output_. La pregunta más importante es cómo hacer que el proceso _input_ y cómo registrar varias clases de recursos en la misma ruta. –

+0

@Volodymyr: registrar varias clases de recursos bajo la misma ruta? Eso seguramente * no * puede ser RESTful! El punto es que estás exponiendo muchas vistas del mismo recurso subyacente. Esas diferentes presentaciones de JSON deben ser simplemente formas diferentes de ver una cosa. (A los serializadores se les debe enseñar cómo construir las diferentes vistas, pero eso es lo que se obtiene al ir a este tipo de complejidad). –

+0

Y para ingresar (es decir, la anotación '@ Consumes') solo tiene que lidiar con lo que He sido dado. Los clientes tienden a odiarlo si arrojas lo que les devolvieron en la cara (cuando tuve un código que hizo eso, causó grandes connotaciones para mi colega que estaba escribiendo la biblioteca del cliente complementario ...) –

Cuestiones relacionadas