Hoshi
Hoshi

Reputation: 607

How to maintain versions of RESTful API in Java?

I'd like to implement versioning in my RESTful web service API. I intend to put the version into the URL, viz.: /api/v3/endpoint

What is the ideal way to do this (in Java)?

Although this irritates my aversion to manual version control, my best guess is to save the API interface into a new file and include a bunch of comments to defend against too much entropy:

/** Do not leave more than 2 previous versions in existence! **/
@Path("/api/v3")
public interface RestfulAPIv3
{
    int version = 3;

    @Path("/resources")
    @Method(GET)
    public Response getResources();
}

Of course the idea would be not to copy the implementation also, but to allow it to support multiple versions. This might require moving identical signatures forward to the newer versions so no collisions would happen across interfaces in the class file:

public class RestfulAPIImpl implements RestfulAPIv3, RestfulAPIv2
{
    public Response getResources()
    {
        List<Resource> rs = HibernateHelper.getAll(Resource.class);
        // Can we do something with v2/v3 diffs here?
    }

    @Deprecated
    public Response getOptions()  // ONLY in v2!
    {
         return HibernateHelper.getOptions();
    }
}

Thinking it through, I have no idea how we'd know which version of an endpoint the client has called, except maybe forwarding the request into the methods which is not my favorite thing.

So, my question is - what have all the versioned API implementers been doing to keep all this stuff from getting out of hand? What's the best way to do this? Am I on the right track?

(Note: this other question is about the 'if' - my question is about the 'how'.)

Upvotes: 1

Views: 1699

Answers (1)

Gabriel Saca
Gabriel Saca

Reputation: 150

An alternative to not passing forward a parameter specifying the version number is to add an annotation to the method so that it automatically captures that information and saves it on a request object that can be read elsewhere.

Taking into account that your API might have requests with parameters that differ amongst versions and also have responses that look different you might have to have multiple controllers and view-model classes, one for each version of the API.

UPDATE

As per request, follows some sample code (I've used Play Framework 2.4).

So the objective is to achieve something like this in a controller class:

@Versioned(version = 0.1)
public Result postUsers() {
    // get post data
    UsersService service = new UsersService(getContext());
    service.postUsers(postData);
    // return result
}

And like this in a service class:

public class UsersService extends Service {

    public UsersService(RequestContext context) {
        super(context);
    }

    public ReturnType postUsers() {
        double apiVersion = getContext().getAPIVersion();
        // business logic
    }
}

In order to accomplish that, you would have a Versioned annotation:

import java.lang.annotation.*;
import play.mvc.With;

@With(VersioningController.class)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Versioned {
    double version();
}

And a VersioningController:

import play.libs.F.Promise;
import play.mvc.*;

public class VersioningController extends Action<Versioned> {
    public final static String API_VERSION = "version";

    @Override
    public Promise<Result> call(Http.Context context) throws Throwable {
        context.args.put(API_VERSION, configuration.version());
        return delegate.call(context);
    }
}

And a RequestContext to help you manage that (you could also use the request context to manage the request timestamp, the user requesting the operation, etc):

public class RequestContext {
    private double version;

    public RequestContext(Double version) {
        setAPIVersion(version);
    }

    public double getAPIVersion() {
        return version;
    }

    public void setAPIVersion(double version) {
        this.version = version;
    }
}

Moreover, your controllers could have a GenericController from which they all extend:

import play.api.Play;
import play.mvc.*;
import play.mvc.Http.Request;

public abstract class GenericController extends Controller {

    protected static RequestContext getContext() {
        return new RequestContext(getAPIVersion());
    }

    protected static double getAPIVersion() {
        return (double) Http.Context.current().args
                .get(VersioningController.API_VERSION);
    }
}

And an abstract Service from which all service classes extend:

public abstract class Service {
    private RequestContext context;

    public Service(RequestContext context) {
        setContext(context);
    }

    public RequestContext getContext() {
        return context;
    }

    public void setContext(RequestContext context) {
        this.context = context;
    }
}

Having all that said, keep in mind that it could be better to try to isolate the API versioning in as few layers as possible. If you can keep your business logic classes from having to manage API versions it's all the better.

Upvotes: 2

Related Questions