d0x
d0x

Reputation: 11601

How to configure a Spring REST Service to handle multiple versions?

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:

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

Answers (2)

Eugen
Eugen

Reputation: 8793

Versioning a REST API is a complex problem. First, let's identify some high-level approaches to versioning:

  • URI versioning - Resources are considered immutable and we create a new URI space changes in the Representation of Resources using version indicators
  • Language extension/versioning - considering that it's the Representation of the Resource that is changing, this solution will version the Representation itself without impacting the URI space

With that in mind, let's consider some goals: (straight out of API Evolution)

  • keep compatible changes out of names
  • avoid new major versions
  • makes changes backwards-compatible
  • think about forwards-compatibility

Next, let's consider some possible changes to the API:

1. Adding to the Representation of a Resource

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.

2. Removing or changing an existing Representation

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.

3. Standard Incompatible Changes

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.

4. Major Incompatible Changes

For such sweeping changes, version indicators in the URI are the last resort solution.

On the technical aspect, I found that:

  • the DAO and Service layer should not change based on the version
  • using a Facade layer (and DTOs) on top of the Service layer will mean that each version will have its own Facades and DTOs
  • to cleanly separate the two versions, having two web contexts with two DispatcherServlets may make sense as it allows for a clean separation of the URI spaces
  • component scanning should be fine-grained and only pick up what is relevant for that particular version in that context
  • the @RequestMapping new attributes - produces and consumes are also helpful

Some other very useful resources:

Hope this helps.

Upvotes: 10

Pavel Horal
Pavel Horal

Reputation: 18224

This is what we are doing on our projects:

  • packages
    • {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 entities

If 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

Related Questions