Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • http://api.com/v1/questions
  • http://api.com/v2/questions
  • ..

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:

  • how to organize the different ~.web.* @Controller from a java package point of view?
  • how to annotate the different ~.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?
  • how to organize the converter? Where to put them, how to name them? Smth. like QuestionsV1ToV2 Controller?
  • Is there a DTO layer requered? Because our Domain has to deal with many versions as the same time?

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);
    }
}
like image 855
d0x Avatar asked Jun 20 '13 07:06

d0x


People also ask

How do I keep my spring boot application versioning?

Through a URI path – you include the version number in the URL path of the endpoint, for example, /api/v1/persons. Through query parameters – you pass the version number as a query parameter with a specified name, for example, /api/persons? version=1.


1 Answers

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:

  • Version Compatibility Strategies

Hope this helps.

like image 200
Eugen Avatar answered Nov 06 '22 18:11

Eugen