How to Manage Rest API Versioning with Spring

How to manage REST API versioning with spring?

Regardless whether versioning can be avoided by doing backwards compatible changes (which might not always possible when you are bound by some corporate guidelines or your API clients are implemented in a buggy way and would break even if they should not) the abstracted requirement is an interesting one:

How can I do a custom request mapping that does arbitrary evaluations of header values from the request without doing the evaluation in the method body?

As described in this SO answer you actually can have the same @RequestMapping and use a different annotation to differentiate during the actual routing that happens during runtime. To do so, you will have to:

  1. Create a new annotation VersionRange.
  2. Implement a RequestCondition<VersionRange>. Since you will have something like a best-match algorithm you will have to check whether methods annotated with other VersionRange values provide a better match for the current request.
  3. Implement a VersionRangeRequestMappingHandlerMapping based on the annotation and request condition (as described in the post How to implement @RequestMapping custom properties
    ).
  4. Configure spring to evaluate your VersionRangeRequestMappingHandlerMapping before using the default RequestMappingHandlerMapping (e.g. by setting its order to 0).

This wouldn't require any hacky replacements of Spring components but uses the Spring configuration and extension mechanisms so it should work even if you update your Spring version (as long as the new version supports these mechanisms).

spring rest api versioning

If you want to do use separate method for both version you can by defining different value in @RequestMapping

method 1

    @RequestMapping(
value = "baseurl/v1/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)

and

@RequestMapping(
value = "baseurl/v2/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)

but if you want to handle it in same method you can using

method 2: use @PathVariable

@RequestMapping(
value = "baseurl/{version}/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public returnType methodName(@PathVariable("version") String version){

// code to check version

}

method 3: use @RequestHeader

@RequestMapping(
value = "baseurl/role",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public returnType methodName(@RequestHeader("version") String version){

// code to check version

}

But as you said you have different version of api's prefer to manage them in separate controller using @RequestMapping at class level

method 4:

@RestController
@RequestMapping("/v1")
public class anyController {

}

and

@RestController
@RequestMapping("/v2")
public class anyController {

}

How to manage REST API versioning with spring data rest?

This is an interesting topic. The documentation of spring-data-rest does not mention best-practices for versioning.

From what I know about spring-data-rest it is not possible to implement media type versioning without implementing custom controllers.

But of course the current functionality allows for URI based versioning:

  • always include a version in your entity - either in the package or class name - e.g. com.company.v1.MyEntity or com.company.MyEntityV1
  • in the repository use @RepositoryRestResource to contain the version in the URI

    @RepositoryRestResource(path = "/v1/shops")

So you could always create a new entity version when you have breaking changes on the API and use a new repository to expose the new version on the API.

EDIT:
We were discussing this topic in the team and a few new interesting thoughts on REST API versioning came up that I would like to share.

I think we all agree that the idea outlined above is not an elegant one. It introduces a lot of code duplication and when you switch off the old version you have to remove it from the code base - which can be harder than you think it is. In the meantime you have to maintain two versions in the code base. You would always want to avoid that.

So what are the alternatives. You could do what we often do in a microservices world - push complexity to the infrastructure. Creating and running new runtimes is fairly easy in a microservices infrastructure so we could choose to run two different versions of the same service. The old version providing the old API version and the new one supporting our new version. An API Gateway could take over the routing e.g. based on HTTP headers (Accept or Content-Type) or version information contained in the URI. And if you want to get rid of the old version you just have to switch off the runtimes that run the old version and you are done. I think this can be an elegant solution that keeps your code base clean.

There are chances that your infrastructure already supports running different versions of the same service to support blue-green deployment scenarios. Which means you already have some routing capabilities that you could build up on.

Also I think if you support blue-green deployment scenarios (which you should) you have to keep database schema versions compatible between releases (e.g. your old version of your service needs to be able to run on the new schema version). If you have to keep this level of compatibility it might not be too hard to avoid REST API versioning most of the times. So you are not forced to do it often - maybe only if you make fundamental changes (that you would not do in the same codebase anyway).

Custom Header Approach for Spring Boot REST API versioning

this is how this approach works.

@RestController
public class TestController {

@RequestMapping(value = "/user")
public String getUserDefault() {
return "getUserDefault";
}

@RequestMapping(value = "/user", headers = {"X-API-VERSION=v1"})
public String getUserV1() {
return "getUserV1";
}

@RequestMapping(value = "/user", headers = {"X-API-VERSION=v2"})
public String getUserV2() {
return "getUserV2";
}

}

also, you can use GetMapping instead of RequestMapping, let's test it using curl:

curl --location --request GET 'http://localhost:8080/user' --header 'X-API-VERSION: v2'

return 200 getUserV2

curl --location --request GET 'http://localhost:8080/user' --header 'X-API-VERSION: v1'

return 200 getUserV1

curl --location --request GET 'http://localhost:8080/user'

return 200 getUserDefault


Related Topics



Leave a reply



Submit