Jonathan Jonathan - 3 months ago 12
Java Question

REST Api Implementation Using Spring

One of the best practices of writing RESTFul api application, is to add versioning. for example:

http://my-server/api/v1/getData
http://my-server/api/v2/getData


Our application exposes REST api using the Spring framework. We mark a class as a Controller, use RequestMapping annotation to map URL to a function, and add some objects that are translated to / from json objects.

For example:

@RequestMapping(method = RequestMethod.POST, value = "/api/v1/getData")
public @ResponseBody ResponseDataDTO getData(@RequestBody OperationsDetailsTDO details) {...}


Now, we want to provide the second version of the API. Around 2/3 of the functions remain the same, and 1/3 are changing. The changes are in both logic and the JSON objects.

I wonder how to design the code. I think that this kind of code is hard to manage:

@RequestMapping(method = RequestMethod.POST, value = "/api/{version-var}/getData")
public @ResponseBody ResponseDataDTO createReleaseFromTemplate(@PathVariable("version-var") Integer version, @RequestBody OperationsDetailsTDO details) {
if (version == 1)
{
doForVersion1();
}
else if (version == 2)
{
doForVersion2();
}

}


It will be hard to manage, since in each function there will be different branching. Just to demonstrate the problem, if I have an automatic tool that generates documentation - it won't be able to understand what is the API.

Second, I wonder what should I do with the classes that are binded to JSON object. Do I need to duplicate all of these classes, for minor changes?

Thx.

Answer

I agree with you on passing version as a parameter, just like

@RequestMapping(method = RequestMethod.POST, value = "/api/{version-var}/getData")

But I don't think it's a good idea to add lots of branches, We should extract all methods in the resource class to a business interface, such as,

private IDataRetrieve dataRetriever;
@RequestMapping(method = RequestMethod.POST, value = "/api/{version-var}/getData")
public @ResponseBody ResponseDataDTO createReleaseFromTemplate(@PathVariable("version-var") Integer version, @RequestBody OperationsDetailsTDO details) {
    dataRetiever = DataRetrieverFactory.getDataTrieverByVersion(version);  //TODO, create a factory to get DataRetriever
    return dataRetiever.getData();
}

And then you need two classed to implement IDataRetriver, (one for V1, another for v2); of cause, to avoid duplicated code, you can add a abstract class for V1 and V2, and use Template Patern to remove the duplicated code.