实际中的REST Web服务版本控制
问题内容:
我正在创建一个新的Web服务,并且已经阅读了APIgee的一些电子书,其中建议对Web服务进行版本控制。我了解将版本控制信息保留在URL与标头之间存在一些“争斗”。通过阅读和理解,我想在标题中使用版本控制。
我的问题是;实际情况如何?我正在使用Spring MVC 3.2。您是否只是在响应不同版本的同一控制器中创建了这样的方法?
版本1:
@RequestMapping(method = RequestMethod.GET, produces = "application/vnd.example-v1+json")
版本2:
@RequestMapping(method = RequestMethod.GET, produces = "application/vnd.example-v2+json")
还是这是错误的?还是创建包含不同版本控制器的其他软件包更常见?还是有其他方法?
问题答案:
这里的问题与版本信息的存放位置(URI与标头)无关,而与如何组织不同版本的代码有关。
我怀疑是否有单一的标准方法。这仅取决于版本的差异。
简单的格式更改。
例如,假设唯一的区别是您从V1中的XML迁移到V2中的JSON。在这种情况下,您可以使用完全相同的代码,但只需将应用程序配置为全局输出JSON。无需其他程序包或控制器。(例如,您可以使用JAXB注释来驱动XML和Jackson生成的JSON输出。)
适度的架构更改。
假设V2引入了少量重大的模式更改。在这种情况下,在上面创建新包可能没有任何意义。您的控制器中可能只有简单的条件逻辑,可以处理/提供版本的正确表示。
主要模式更改。
如果您的模式更改是深远而广泛的,那么您可能需要的不仅仅是单独的控制器。您甚至可能需要其他域模型(实体/服务)。在这种情况下,对于控制器一直到实体,仓库甚至数据库表,都有一套并行的软件包可能是有意义的。
应用想法
方法1.
将这些想法应用到@RequestMapping
示例中,您可以按照说明进行操作,但是如果版本之间的响应完全相同,则它们应该仅委托给一个共享方法:
@RequestMapping(
value = "/orders/{id}",
method = RequestMethod.GET,
produces = "application/vnd.example-v1")
@ResponseBody
public Order getOrderV1(@PathVariable("id") Long id) {
return getOrder(id);
}
@RequestMapping(
value = "/orders/{id}",
method = RequestMethod.GET,
produces = "application/vnd.example-v2")
@ResponseBody
public Order getOrderV2(@PathVariable("id") Long id) {
return getOrder(id);
}
private Order getOrder(Long id) {
return orderRepo.findOne(id);
}
这样的事情会起作用。如果版本之间的顺序不同,则可以直接在方法中实现差异。
方法2。 您可能要尝试的另一件事-我自己也没有尝试过-
每个资源类型(例如订单,产品,客户等)都有自己的基本控制器,并带有HTTP方法的方法级别注释(只是value
和method
定义,但没有produces
)。然后,使用特定于版本的扩展来扩展基础,扩展控制器@RequestMapping(value = "/orders", produces = "application/vnd.example-v1")
在类级别具有基础。然后仅覆盖版本和基准之间的增量。 我不确定这是否行得通,
但是如果这样的话,这将是组织控制器的一种非常干净的方法。这就是我的意思:
// The baseline
public abstract class BaseOrderController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
public Order getOrder(@PathVariable("id") Long id) { ... }
}
// V1 controller
@RequestMapping(value = "/orders", produces = "application/vnd.example-v1")
public class OrderControllerV1 extends BaseOrderController {
... no difference from baseline, so nothing to implement ...
}
// V2 controller
@RequestMapping(value = "/orders", produces = "application/vnd.example-v2")
public class OrderControllerV2 extends BaseOrderController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
@Override
public Order getOrder(@PathVariable("id") Long id) {
return orderRepoV2.findOne(id);
}
}