12

Best practice for REST resource versioning is putting version information into Accept/Content-Type headers of HTTP request leaving URI intact.

Here is the sample request/response to REST API for retrieving system information:

==>
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
}

Pay attention that version is specified in MIME type.

Here is another request/response for version 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
}

See http://barelyenough.org/blog/tag/rest-versioning/ for more explanation and examples.

Is it possible to implement this approach easily in Java-targeted JAX-RS based implementations, such as Jersey or Apache CXF?

The goal is to have several @Resource classes with the same @Path value, but serving the request based on actual version specified in MIME type?

I've looked into JAX-RS in general and Jersey in particlaur and found no support for that. Jersey doesn't give a chance to register two resources with the same path. Replacement for WebApplicationImpl class needs to implemented to support that.

Can you suggest something?

NOTE: It is required for multiple versions of the same resource needs to be available simultaneously. New versions may introduce incompatibale changes.

Volodymyr Tsukur
  • 275
  • 1
  • 3
  • 10
  • 3
    This definitely NOT best practice for versioning an API. The best practice is to NOT have versions and only make compatible changes. Artificially creating new MIME types for changes that every sensible client should deal with automatically (adding new tags/keys to your data) is not RESTful at all in my book. – Jochen Bedersdorfer Feb 07 '11 at 17:34
  • 4
    Well, it is NOT always possible to make compatible changes. Moreover,, in my case multiple REST resource versions need to be supported simultaneously. As far as resource identity must be preserved URI must change the same. New version is the new representation of the resource, i.e. new MIME type. – Volodymyr Tsukur Feb 07 '11 at 20:35
  • Thank you for your comment, I'll update original question to be more specific – Volodymyr Tsukur Feb 07 '11 at 20:39
  • 1
    @Jochen Ideally you should never have to version. That should be the goal. However, if it does become necessary then I would say this is best way to handle it. – Darrel Miller Feb 07 '11 at 21:31
  • 'becomes necessary' is a bit too broad to comment on that. If you completely change your presentations and paths, then having new MIME types is probably a must. If you follow the HATEOAS paradigm, this pain can be greatly reduced. (i.e. don't assume the structure of URLs, let the hypertext guide you, etc.) – Jochen Bedersdorfer Feb 07 '11 at 21:37
  • I disagree in that versioning is something to avoid: however, maybe there is miscommunication here. My understanding of best practices is that a given URI always refers to specific immutable version; and that versioning is handled by using different URI. This is usually done by using version identifier as part of path. – StaxMan Feb 07 '11 at 21:53
  • @Jochen We do completely change presentations but want stick to the same resource URI – Volodymyr Tsukur Feb 07 '11 at 22:00
  • For original question it is more important to understand how JAX-RS fits into the versioning implementation based on MIME types and content negotation. – Volodymyr Tsukur Feb 07 '11 at 22:01
  • Thanks for the clarification. If you need to keep different versions of your presentations around because clients can't deal with the differences between those versions, you probably need to go that route. I would certainly like to avoid that if possible. No fun in having to maintain and document and debug different versions. – Jochen Bedersdorfer Feb 07 '11 at 22:18
  • Yes, it's not that easy to support, but inevitable due to domain specifics. Thank you for your comments! – Volodymyr Tsukur Feb 07 '11 at 22:24
  • @StaxMan Nah, versioning in the URL is even more evil. – Darrel Miller Feb 07 '11 at 23:22
  • 2
    Darrel: this is exactly how URLs are used by specifications by IETF for example, as well as many public web services. It is an obvious and simple solution for versioning. Nothing evil in there whatsoever. It may not work for this use case, but is used by everyone else. – StaxMan Feb 08 '11 at 00:27
  • @StaxMan Resource URIs in REST should be permalinks and should not change (unless it is not a completely new resource, of course). Versions are in essence different representations. We want to stick to that rule unless it is too complex to implement. Thank you for your comments! – Volodymyr Tsukur Feb 08 '11 at 06:54
  • @Volodymyr I can see how there can be difference between versions thought of as representations vs new resources -- both have their use cases. Key thing is that caller then explicitly defines information to get exact version (i.e. no one gets "accidentally upgrade"). So this makes sense to me too. – StaxMan Feb 08 '11 at 18:07
  • Yes, caller specifies exact version. There are no major accidental upgrades. – Volodymyr Tsukur Feb 09 '11 at 06:51
  • 2
    @StaxMan the nice thing about doing versions in the MIME type is that you can pass a URL over to a different client which is on a newer version of the API, and it will still work. (this is just one example where it is clearly a benefit) – aehlke Feb 23 '11 at 15:27

5 Answers5

6

JAX-RS dispatches to methods annotated with @Produces via the Accept header. So, if you want JAX-RS to do your dispatching, you'll need to leverage this mechanism. Without any extra work, you would have to create a method (and Provider) for every media type you wish to support.

There's nothing stopping you from having several methods based on media type that all call a common method to do that work, but you'd have to update that and add code every time you added a new media type.

One idea is to add a filter that "normalizes" your Accept header specifically for dispatch. That is, perhaps, taking your:

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

And converting that to, simply:

Accept: application/vnd.COMPANY.systeminfo+json

At the same time, you extract the version information for later use (perhaps in the request, or some other ad hoc mechanism).

Then, JAX-RS will dispatch to the single method that handles "application/vnd.COMPANY.systeminfo+json".

THAT method then takes the "out of band" versioning information to handle details in processing (such as selecting the proper class to load via OSGi).

Next, you then create a Provider with an appropriate MessageBodyWriter. The provider will be selected by JAX-RS for the application/vnd.COMPANY.systeminfo+json media type. It will be up to your MBW to figure out the actual media type (based again on that version information) and to create the proper output format (again, perhaps dispatching to the correct OSGi loaded class).

I don't know if an MBW can overwrite the Content-Type header or not. If not, then you can delegate the earlier filter to rewrite that part for you on the way out.

It's a little convoluted, but if you want to leverage JAX-RS dispatch, and not create methods for every version of your media type, then this is a possible path to do that.

Edit in response to comment:

Yea, essentially, you want JAX-RS to dispatch to the proper class based on both Path and Accept type. It is unlikely that JAX-RS will do this out of the box, as it's a bit of an edge case. I have not looked at any of the JAX-RS implementations, but you may be able to do what you want by tweaking one of the at the infrastructure level.

Possibly another less invasive option is to use an age old trick from the Apache world, and simply create a filter that rewrites your path based on the Accept header.

So, when the system gets:

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

You rewrite it to:

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

Then, in your JAX-RS class:

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

So, your clients get the correct view, but your classes get dispatched properly by JAX-RS. The only other issue is that your classes, if they look, will see the modified Path, not the original path (but your filter can stuff that in the request as a reference if you like).

It's not ideal, but it's (mostly) free.

This is an existing filter that might do what you want to do, if not it perhaps can act as an inspiration for you to do it yourself.

Will Hartung
  • 115,893
  • 19
  • 128
  • 203
  • Thank you very much for the answer. This is getting closer to what I need. Transforming MIME will do the part of the job. However I don't want the Resource method to deal with versioning information. Whole Resource class should represent specific Resource version AND there will be several Resource classes in the runtime, e.g. RestService in bundle 1.0 and RestService in bundle 2.0, both having @Path('/rest'). I want to instruct Jersey to differenciate between the two without rewriting WebApplicationImpl if possible. (if not possible then I will move on and rewrite / extend it) – Volodymyr Tsukur Feb 07 '11 at 22:14
  • This is probably the best answer up till now. Ideally, I will not have versions in @Path, @Produces and resource class names for my class since version should be taken from OSGi bundle version specifier. But again, that's IDEAL scenario. You gave you very useful hints here. Thank you! – Volodymyr Tsukur Feb 08 '11 at 07:00
1

With current version of Jersey, I would suggest an implementation with two different API methods and two different return values that are automatically serialised to the applicable MIME type. Once the requests to the different versions of the API are received, common code can be used underneath.

Example:

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;

@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public VersionOneDTO get(@PathParam("id") final String id) {

    return new VersionOneDTO( ... );

}

@GET
@Path("/{id}")
@Produces("application/vnd.COMPANY.systeminfo-v2+json;qs=0.9")
public VersionTwoDTO get_v2(@PathParam("id") final String id) {

    return new VersionTwoDTO( ... );

}

If method get(...) and get_v2(...) use common logic, I would suggest to put that in a common private method if it's API related (such as session or JWT handling) or else in a common public method of a Service Layer that you access via inheritance or Dependency Injection. By having two different methods with different return types, you ensure that the structure returned is of correct type for the different versions of the API.

Note that some old client may not specify Accept header at all. That means implicitly that they would accept any content type, thus any version of your API. In practice, this is most often not the truth. For this reason you should specify a weight to newer versions of the API using the qs extension of the MIME type as shown in the @Produces annotation in the example above.

If you are testing with restAssured it would look something like this:

import static com.jayway.restassured.RestAssured.get;
import static com.jayway.restassured.RestAssured.given;

@Test
public void testGetEntityV1() {
    given()
        .header("Accept", MediaType.APPLICATION_JSON)
    .when()
        .get("/basepath/1")
    .then()
        .assertThat()
        ... // Some check that Version 1 was called
    ;
}

@Test
public void testGetEntityV1OldClientNoAcceptHeader() {
    get("/basepath/1")
        .then()
        .assertThat()
        ... // Some check that Version 1 was called
    ;
}

@Test
public void testGetEntityV2() {
    given()
        .header("Accept", "application/vnd.COMPANY.systeminfo-v2+json")
    .when()
        .get("/basepath/1")
    .then()
        .assertThat()
        ... // Some check that Version 2 was called
    ;
}
Andreas Lundgren
  • 12,043
  • 3
  • 22
  • 44
0

One possible solution is to use one @Path with

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

Then, inside the method of the given @Path you can call the version of the WebService

Diego Dias
  • 21,634
  • 6
  • 33
  • 36
  • The problem is that I don't want to move version-specific logic right inside the method implementation. I want that to be handled outside. – Volodymyr Tsukur Feb 07 '11 at 17:24
  • Either way you will have to "create a layer to choose which method to call". The way you are proposing this layer will call a specific method based on the @Consumes content. The solution I posted you will have just one @Consumes and choose the method to call inside it. For me its just the same, one way you handle with annotations and replicate on methods. The other way you replicate method calls. – Diego Dias Feb 07 '11 at 18:16
  • Well, the problem is that there HAVE to be multiple resource classes with the same @Path. All these classes should be provided by OSGi bundles of different versions, all of them may be active. Resource class cannot be the entry point for versioning-selector logic. – Volodymyr Tsukur Feb 07 '11 at 21:19
  • I think it is basically a bad idea to use same URI (path) for incompatible versions; even if it seems like more convenient way to do things. Instead of trying to fight this, maybe you could refactor things to reduce duplication, and just consider entry method to be simple wrapper, delegating to shared functionality. – StaxMan Feb 07 '11 at 21:55
  • @StaxMan Unfortunately, simple wrapper that delegates to shared functionality will not resolve the issue here – Volodymyr Tsukur Feb 07 '11 at 22:25
  • Ah. I guess I should have guessed that problem to solve is rather complex, given suggested solution. I hope you will figure it out either way. – StaxMan Feb 07 '11 at 23:04
0

If you're using CXF, you could use the technique specified here to build a new serialization provider (building off the existing infrastructure) which produces the data in the specific format desired. Declare a couple of those, one for each specific format that you want, and use the @Produces annotation to let the machinery handle the rest of the negotiation for you, though it might also be an idea to support the standard JSON content type too so that normal clients can handle it without needing to grok your specialness. The only real question then becomes what is the best way to do the serialization; I presume you can figure that out for yourself…


[EDIT]: Further digging in the CXF documentation leads to the revelation that both the @Consumes and @Produces annotations are considered to be axes for doing selection. If you want to have two methods that handle the production of the response for different media types, you most certainly can. (You'll have to add the serialization and/or deserialization providers if you're using custom types, but you can do the delegation of the majority of the work to the standard providers.) I'd still like to caution that you should still ensure that the resource indicated by the path should be the same in both cases; to do otherwise is not RESTful.

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
  • Donal, thank you for the response. You've pointed at hint to serialization provider, which is part of the _output_ preparation. More important question is how to make process _input_ and how to register several Resource class under the same path. – Volodymyr Tsukur Feb 07 '11 at 22:32
  • @Volodymyr: Register several resource classes under the same path? That surely *can't* be RESTful! The whole point is that you're exposing many views of the same underlying resource. Those different JSON presentations must be simply different ways of viewing the one thing. (The serializers have to be taught how to construct the different views, but that's what you get for going to this sort of complexity.) – Donal Fellows Feb 08 '11 at 10:05
  • And for input (i.e., the `@Consumes` annotation) you just have to deal with what you've been given. Clients tend to hate it if you throw what they've sent back in their face though (when I had code that did that, it caused great conniptions for my colleague who was writing the companion client library…) – Donal Fellows Feb 08 '11 at 10:08
  • Resource should be the same, that's true. I am not violating REST. In my case each Resource class is aimed to provide different representations. Versioning is tied to representations in this case. But new representations (Resource classes) can come in from OSGi bundles. Even class name may br the same. This is a very dynamic system. I don't want to introduce my own framework around that since levereging JAX-RS is very useful. – Volodymyr Tsukur Feb 08 '11 at 11:53
0

You should be able to use different classes with the same path provided they consume/produce different media types. So this should work with any jax-rs provider:

@Path("/api/system-info")
@Consumes("application/vnd.COMPANY.systeminfo-v1+json")
@Produces("application/vnd.COMPANY.systeminfo-v1+json")
public class SystemInfoResourceV1 {
}

and

@Path("/api/system-info")
@Consumes("application/vnd.COMPANY.systeminfo-v2+json")
@Produces("application/vnd.COMPANY.systeminfo-v2+json")
public class SystemInfoResourceV2 {
}
cghislai
  • 1,751
  • 15
  • 29