REST Microservice API Versioning Strategy

article

Today we will see how we can implement different version of same service in REST terminology. Now a days we have a boom of developing smaller services using REST and in microservice eco system, those services interact with each other and produce the final business output. Now once this eco system becomes big and business require many new functionalities, sometimes we need to implement different version of same service so that the existing clients don’t get affected and at the same time new functionalities are available so that new clients can consume that. Today we will see by example on how we can implement versioning in REST based service.

What is versioning and why it is important?

In short, versioning means maintaining different endpoints of same functionality in the service boundary layer and different consumer can consume different version of service based on the need. Now, it is important to understand why it is required? As a best practice we should look for strategy not to introduce versioning too so often to minimize the maintainability issues, rather we should design our services in such a way that they are backward compatible.

But in reality, we face some of the situations where we actually need to introduce versioning of our services, let’s think about below scenarios

  1. We have two consumer application for same service and we need to introduce some extra parameter in request to meet the requirement of either of the applications.
  2. We need to change the processing based on the consumer application.
  3. We need to change the response in a way that is not acceptable to one of the consumer applications
  4. And many more, if we might face.

So, in short, we need versioning (although this should be the last option) of our services in certain scenarios.

Now, let’s understand different strategies of versioning microservices. Before start about the strategy, let’s first define the service functional details and technology stack.

Technology Stack

We will use the below technology stack for our demonstration

  1. Spring boot – as application framework for exposing REST service
  2. Outsource Java development, Maven, eclipse as standard development environment
  3. REST – as we are discussing on REST only

What we will demonstrate

Today we will create a simple REST service which will return some Product details based on the product ID in the request, we need to implement different versions of this service, in the following sections we will see different approaches of this and also, we will see how things work with a real spring-boot project example. So, let’s get started.

What are the different approaches of REST service versioning

There are mainly four types of approaches in REST Api versioning as below, each method is popular nut we need to take the call based on our actual scenario and standard. But we should not mix the approach in different services in same domain, we should choose one approach and stick to that approach in all the services of that project.

Here are the approaches in high level, we will see those in action in the demonstration section.

URL Versioning

In this approach we create different URL for the service and consumers use the appropriate URL to consume the service. This is the simplest approach and extensively followed.

Example URL of this could be like

  • /product/v1/{id}
  • /product/v2/{id}

As we can see we are basically changing the endpoint URL of the service method by introducing some version identifier. This is easier but has some side effects like

  • So many versions on same method will create URL pollution, will create more confusion among the consumers, so proper documentation is very much required.
  • It is easier to test if the request is GET type, specially of we have some non-technical users like Business users who want to try out the service in browser itself, other approaches may not be accessible through browser.

Headers Versioning

In this approach, we generally specify custom header in the request and server side identify the actual controller method through this custom header.

Example URL of this could be like

  • /prduc/{id}
    • headers=[ PRDUCT-API-VERSION=1]
  • /prduc/{id}
    • headers=[PRDUCT-API-VERSION=2]

This approach is also simple, only thing we need to document properly so that consumers sent the headers properly to access the required service version. But unlike URL versioning, this can’t be tested/accessed through normal web browser, we need some REST client for that. Also caching can be challenging as we need to consider the request headers as well along with the URL while caching.

Request Parameter versioning

In this approach we keep the base URL same, but we pass version information through request parameter. In the controller side, we segregate the versioned method through this request parameter, proper method got invoked based on the matching RequestParm.

Example URL of this could be like

  • /prd/{id}?version=1
  • /prd/{id}?version=2

Media type versioning

In this approach, we use the existing Accept header to pass version information to the server, consumers need to specify which version it can accept.

Example URL of this could be like

  • /product-accept/{id}
    • headers[Accept=application/vnd.product.app-v1+json]
  • /product-accept/{id}
    • headers[Accept=application/vnd.product.app-v2+json]

REST Api Versioning in action

So that’s all in high level about the REST versioning strategies. Now let’s quickly see the actual code fragments for each strategy. I will upload the working project with this article, so you can quick start by downloading that.

Maven Project Creation

Before starting the actual code, we need to first create one spring boot project where we will test those approaches. The easiest way to do that

  1. Go to https://start.spring.io/, generate one spring boot project skeleton by providing the Artifact id, Group ID and required dependencies, for our testing we need to just select web starter dependency. After entering all that information, we can generate the project using Generate Project button like below.
  2. article
  3. Once we have downloaded the project, we need to unzip that in suitable location and import that project in eclipse or your favorite IDE as maven project. Now try one initial mvn clean install command on that project to ensure that all the spring boot related jars got downloaded properly.
  4. 3. Now we are ready to write our service in this project and test. For each approach of versioning we will create different RestController and will create two service methods for the versioning. This is very straight forward, So I will not go into the details of them in each case, this will unnecessary increase the length of this article, so let’s see the code fragments for each approach below.

Code fragments for different versioning approach

  • URL VersioningAs we have already seen in this approach, we will create different URL with the version information so that right method git called. Below can be such example:
  • package com.example.microserviceversion; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import com.example.microserviceversion.model.Product; import com.example.microserviceversion.model.ProductV1; @RestController public class RestControllerURLMapping { @GetMapping("/product/{id}") public Product getProduct(@PathVariable String id) { System.out.println("Inside RestControllerURLMapping::getProduct - retriving product for id : " + id); Product product = new Product(); product.setProductname("ABC Product"); return product; } @GetMapping("/product/v1/{id}") public ProductV1 getProductV1(@PathVariable String id) { System.out.println("Inside RestControllerURLMapping::getProductV1 - retriving product for id : " + id); ProductV1 product = new ProductV1(); product.setProductname("ABC Product"); product.setProductdescription("This is ABC Product!!"); return product; } }

    We can see that we are using two url here /product/{id} and /product/v1/{id}.

    To test this, we can just hit the service through browser or any RESt Client. In Local url will be

    • http://localhost:8080/product/1
    • http://localhost:8080/product/v1/1
  • Headers VersioningIn this approach, we will use custom header, the controller code will be like this.
  • package com.example.microserviceversion; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import com.example.microserviceversion.model.Product; import com.example.microserviceversion.model.ProductV1; @RestController public class RestControllerHeader { @GetMapping(value = "/prduc/{id}", headers = "PRDUCT-API-VERSION=1") public Product getProductV1WithHeader(@PathVariable String id) { System.out.println("Inside RestControllerHeader::getProductV1WithHeader - retriving product for id : " + id); Product product = new Product(); product.setProductname("ABC Product"); return product; } @GetMapping(value = "/prduc/{id}", headers = "PRDUCT-API-VERSION=2") public ProductV1 getProductV2WithHeader(@PathVariable String id) { System.out.println("Inside RestControllerHeader::getProductV2WithHeader - retriving product for id : " + id); ProductV1 product = new ProductV1(); product.setProductname("ABC Product"); product.setProductdescription("This is ABC Product!!"); return product; } }

    The testing URL in local will be http://localhost:8080//prduc/1 and we need to pass custom header PRDUCT-API-VERSION with values 1 or 2 to invoke proper service.

  • Request ParameterIn this approach as we have explained, we will pass different Request Parameters in the request to identify the version. Below might be the simplest example of this in controller.
  • package com.example.microserviceversion; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.example.microserviceversion.model.Product; import com.example.microserviceversion.model.ProductV1; @RestController public class RestControllerRequestParm { @GetMapping(value = "/prd/{id}", params = "version=v1") public Product getProductV1WithReq(@PathVariable String id, @RequestParam(name = "version") String v) { System.out.println("Inside RestControllerRequestParm::getProductV1WithReq - retriving product for id : " + id); Product product = new Product(); product.setProductname("ABC Product"); return product; } @GetMapping(value = "/prd/{id}", params = "version=v2") public ProductV1 getProductV2WithReq(@PathVariable String id, @RequestParam(name = "version") String v) { System.out.println("Inside RestControllerRequestParm::getProductV2WithReq - retriving product for id : " + id); ProductV1 product = new ProductV1(); product.setProductname("ABC Product"); product.setProductdescription("This is ABC Product!!"); return product; } }

    The local testing URL would be in this case

    • http://localhost:8080/product/1
    • http://localhost:8080/product/v1/1
  • Media type This is the last one and below is the code snippet. In this approach, we will use the Accept Header to identify the actual version.
  • package com.example.microserviceversion; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import com.example.microserviceversion.model.Product; import com.example.microserviceversion.model.ProductV1; @RestController public class RestControllerAcceptHeader { @GetMapping(value = "/product-accept/{id}", produces = "application/vnd.product.app-v1+json") public Product getProduct(@PathVariable String id) { System.out.println("Inside RestControllerAcceptHeader::getProduct - retriving product for id : " + id); Product product = new Product(); product.setProductname("ABC Product"); return product; } @GetMapping(value = "/product-accept/{id}", produces = "application/vnd.product.app-v2+json") public ProductV1 getProductV1(@PathVariable String id) { System.out.println("Inside RestControllerAcceptHeader::getProductV1 - retriving product for id : " + id); ProductV1 product = new ProductV1(); product.setProductname("ABC Product"); product.setProductdescription("This is ABC Product!!"); return product; } }

    The testing url will be http://localhost:8080/product-accept/1 and we need to pass Accept header value as either application/vnd.product.app-v1+json or application/vnd.product.app-v2+json to invoke the expected version of the service.

Final note:

So today we have seen how we can implement REST Api versioning with different approach with actual code. But as I have mentioned the versioning stuff could be tricky in some cases and right versioning strategy should be adopted based on the situation and we should sick to that approach throughout that project to minimize the complexity. Finally introduce version when it is required.

Another point I want to highlight is that, from the controller layer, sometimes we receive Request Body as JSON and we receive that as an POJO in controller method, now if different version has different yet mostly similar Request Body then we can receive those request body in different POJO in different controller method, but we should convert that to a common DTO before passing on the further layers to avoid duplicate code in other layers as well. Try to minimize version specific logic in different layers.