Java 如何配置SpringREST服务来处理多个版本?
几乎所有的API都处理不同的发布版本。您经常会看到这种版本控制:Java 如何配置SpringREST服务来处理多个版本?,java,spring,rest,spring-mvc,spring-ws,Java,Spring,Rest,Spring Mvc,Spring Ws,几乎所有的API都处理不同的发布版本。您经常会看到这种版本控制: 但是我还没有找到一个描述如何在Spring堆栈中组织它们的源代码。我想在每个控制器上都有/v1前缀,比如@RequestMapping(“/v1/questions”)并不是最好的方法 想象一下,只有当前版本(在我们的例子中是V2)有一个@Service层 我们的服务应该处理V1和V2的请求。唯一的变化是V2在问题实体上添加了一个新字段(这意味着V1问题可以轻松转换为V2问题) 现在的问题是: 如何从java包的角度组
/v1
前缀,比如@RequestMapping(“/v1/questions”)
并不是最好的方法
想象一下,只有当前版本(在我们的例子中是V2)有一个@Service
层
我们的服务应该处理V1和V2的请求。唯一的变化是V2在问题实体上添加了一个新字段(这意味着V1问题可以轻松转换为V2问题)
现在的问题是:
- 如何从java包的角度组织不同的
~.web.*@Controller
- 如何对不同的
进行注释,说明它们知道自己的版本?以~.web.*@Controller
?或者可以在v1java包中使用context:component扫描配置它们吗请求映射的方式
- 如何组织转换器?放在哪里,如何命名?Smth。像问题1到2控制器
- 是否需要DTO层?因为我们的域名必须同时处理多个版本
这就是我们在项目中所做的:
- 包裹
-{developer}.{customer}.{project}.core
和@Service
东西@Dao
-模型实体,这就是整个{developer}.{customer}.{project}.core.model
包的工作原理core
-{developer}.{customer}.{project}.api.rest.v1.resource
rest资源@Controller
-v1api的DTOs{developer}.{customer}.{project}.api.rest.v1.model
-DTO和核心模型实体之间的映射器{developer}.{customer}.{project}.api.rest.v1.mapper
{developer}.{customer}.{project}.api.rest.base
中
在每个REST资源中手动编写请求映射。因此,每个资源在其映射中都有/v1/..
我不认为我们的方法是正确的解决方案,甚至不认为我们的方法接近正确的解决方案。但这很简单。我们将看到v2将如何适应。RESTAPI的版本控制是一个复杂的问题。首先,让我们确定一些高级版本控制方法:
- URI版本控制-资源被认为是不可变的,我们使用版本指示符在资源表示中创建一个新的URI空间
- 语言扩展/版本控制-考虑到正在更改的是资源的表示,此解决方案将在不影响URI空间的情况下对表示本身进行版本化
记住这一点,让我们考虑一些<强>目标< /强>:(直接)< /P>
- 将兼容更改保留在名称之外
- 避免新的主要版本
- 使更改向后兼容
- 考虑向前兼容性
接下来,让我们考虑到API:
的一些<强>可能的更改< /强> 1.添加到资源的表示中 语言的设计应该明确地考虑到向前兼容性,客户端应该忽略他们不理解的信息 因此,向资源的表示添加信息并不是不兼容的更改 2.删除或更改现有表达 这种对语言的扩展/更改可以利用Accept标头和内容协商—表示使用自定义供应商MIME媒体类型进行版本控制。这些文章更深入地讨论了这一点: 因此,这确实代表了客户机的不兼容更改—客户机必须请求新的表示并理解新的语义,但URI空间将保持稳定且不会受到影响 3.标准不兼容更改 这些都是资源的含义和它们之间关系的变化。 在本例中,我们可以考虑更改资源和URI结构之间的映射。但是,这并不一定意味着在URI中使用版本指示符 RESTAPI应该遵守HATEOAS约束——大多数URI应该由客户端发现,而不是硬编码。更改这样的URI不应被视为不兼容的更改-新的URI可以替换旧的URI,客户端将能够重新发现URI并仍然运行 4.不兼容的重大变化 对于这种全面的更改,URI中的版本指示器是最后的解决方案 在技术方面,我发现:- DAO和服务层不应根据版本进行更改
- 在服务层之上使用Facade层(和DTO)意味着每个版本都有自己的Facade和DTO
- 为了清晰地区分这两个版本,使用两个
创建两个web上下文可能是有意义的DispatcherServlets
// on V1 Question look like this: public class project.domain.Question { private String question; } // on v2 Question looks like this: public class project.domain.Question { private String question; private Date creationDate; } @Service public class project.service.QuestionService { public long create(Question q) {...}; public Question read(long id) {...}; public void remove(long id) {...}; public void update(Question qd) {...}; } @Controller @RequestMapping("/v2/question") public class project.web.v2.QuestionController { @Autowired project.service.QuestionService questionService; @RequestMapping(method = RequestMethod.POST) @ResponseBody public long create(Question q) { return questionService.create(q); } } @Controller @RequestMapping("/v1/question") public class project.web.v1.QuestionController { @Autowired project.service.QuestionService questionService; @RequestMapping(method = RequestMethod.POST) @ResponseBody public long create(Question q) { // this will not work, because the v1 haven't had the 'creationDate' field. return questionService.create(q); } }