如何在Java中维护RESTful API的版本?

时间:2015-10-09 15:23:46

标签: java api rest interface version

我想在我的RESTful Web服务API中实现版本控制。我打算将该版本放入网址,即:/api/v3/endpoint

这样做的理想方法是什么(用Java)?

虽然这激怒了我对手动版本控制的厌恶,但我最好的猜测是将API接口保存到一个新文件中并包含一堆注释以防止过多的熵:

/** 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();
}

当然,想法也是来复制实现,但允许它支持多个版本。这可能需要将移动相同的签名转发到更新的版本,以便在类文件中的接口之间不会发生冲突:

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();
    }
}

通过思考,我不知道我们如何知道客户端调用的端点版本,除非将请求转发到我不喜欢的方法中。

所以,我的问题是 - 所有版本化的API实现者都在做些什么来防止所有这些东西失控?最好的方法是什么?我是在正确的轨道上吗?

(注意:this other question是关于'if'的 - 我的问题是'如何'。)

1 个答案:

答案 0 :(得分:2)

不传递指定版本号的参数的替代方法是向方法添加注释,以便它自动捕获该信息并将其保存在可在其他地方读取的请求对象上。

考虑到您的API可能包含不同版本参数的请求,并且响应看起来不同,您可能需要拥有多个控制器和视图模型类,每个版本的API都有一个。

<强>更新

根据要求,请遵循一些示例代码(我使用过Play Framework 2.4)。

所以目标是在控制器类中实现类似的东西:

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

在服务类中这样:

public class UsersService extends Service {

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

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

为了实现这一目标,你需要一个Versioned注释:

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

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

和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);
    }
}

还有一个RequestContext来帮助您管理(您还可以使用请求上下文来管理请求时间戳,请求操作的用户等):

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

此外,您的控制器可以有一个GenericController,它们都可以从中扩展:

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);
    }
}

所有服务类扩展的抽象服​​务:

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

尽管如此,请记住,尝试在尽可能少的层中隔离API版本可能会更好。如果您可以保持业务逻辑类不必管理API版本,那就更好了。