"REST Web服务版本控制实践"
"REST Web服务版本控制实践"
我正在创建一个新的Web服务,我已经阅读了APIgee的一些电子书,其中版本化Web服务是被推荐的。我了解,在URL和头部保留版本信息之间存在一些“争执”。根据我所读及理解的信息,我想在头部中使用版本化。
我的问题是:这在实践中是什么样子的呢?我正在使用Spring MVC 3.2。你只是创建一个响应不同版本的方法,在同一个控制器中就可以了吗?
第一版:
@RequestMapping(method = RequestMethod.GET, produces = "application/vnd.example-v1+json")
第二版:
@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); } }