Reputation: 11601
Almost all APIs are dealing with different release versions. Often you see this kind of versioning:
But I haven't found a source discribing how to organize them in a Spring stack. I guess having a /v1
prefix like @RequestMapping("/v1/questions")
on every Controller is not the best approach.
Imagine there is a @Service
layer of just the current release (in our case V2).
Our Service should handle the requests of V1 and V2. The only change was that V2 added a new field on a question entity (that means a V1 Question could be easily converted to a V2 question).
Now the Questions are:
~.web.* @Controller
from a java package point of view?~.web.* @Controller
that they are aware of their versions? In manner of the RequestMapping
? Or is it possible to configure them with a context:component-scan in a V1 java package?An example could look like this (i added the packages everywhere):
// 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);
}
}
Upvotes: 8
Views: 5655
Reputation: 8793
Versioning a REST API is a complex problem. First, let's identify some high-level approaches to versioning:
With that in mind, let's consider some goals: (straight out of API Evolution)
Next, let's consider some possible changes to the API:
The language should be designed explicitly with forward compatibility in mind and Clients should ignore information they don't understand.
As such, adding information to a Representation of a Resource is not an incompatible change.
Such extensions/changes to the language can leverage the Accept header and Content Negotiation - Representations are versioned using custom vendor MIME media type. These articles goes more in depth on this: API Versioning, Versioning REST Web Services.
As such, this does represent an incompatible change for the Client - which will have to request the new Representation and understand the new semantics, but the URI space will remain stable and will not be affected.
These are changes in the meaning of the Resources and the relations between them. In the case, we can look at changing the mapping between the Resources and the URI structure. However, that still doesn't necessarily mean using a version indicator in the URI.
The REST API should adhere to the HATEOAS constraint - most of the URIs should be DISCOVERED by Clients, not hardcoded. Changing such an URI should not be considered an incompatible change - the new URI can replace the old one and Clients will be able to re-discover the URI and still function.
For such sweeping changes, version indicators in the URI are the last resort solution.
DispatcherServlets
may make sense as it allows for a clean separation of the URI spaces@RequestMapping
new attributes - produces
and consumes
are also helpfulSome other very useful resources:
Hope this helps.
Upvotes: 10
Reputation: 18224
This is what we are doing on our projects:
{developer}.{customer}.{project}.core
- @Service
and @Dao
stuff{developer}.{customer}.{project}.core.model
- model entities, this is what the whole core
package works with{developer}.{customer}.{project}.api.rest.v1.resource
- @Controller
REST resources{developer}.{customer}.{project}.api.rest.v1.model
- DTOs for the v1 API{developer}.{customer}.{project}.api.rest.v1.mapper
- mappers between DTOs and core model entitiesIf core evolves (and it constantly evolves), it concerns only the API mappers. Resources and DTOs stays the same.
Our core and its model changed a lot since we have introduced v1 API. Of course we are doing some backwards-compatible changes to the v1 - introducing new search parameters, resources or parameter based response modifiers.
And I have to say that we have not yet advanced to v2 API - but that is behind the corner as the v1 is already prehistoric and in some aspects incompatible with the core model (different model attributes, different model relations, obsolete and inconsistent naming...).
If we would like to reuse some code between versions, I can imagine that it will be placed in {developer}.{customer}.{project}.api.rest.base
.
The request mapping is manually written in each REST resource. So every resource has /v1/...
in its mapping.
I don't feel 100% confident that our way is the correct solution or even close to it. But it is simple. We will see how the v2 will fit in.
Upvotes: 2