Enable Javascript

Please enable Javascript to view website properly

Toll Free 1800 889 7020

Looking for an Expert Development Team? Take two weeks Trial! Try Now

REST Microservice API Versioning Strategy

article

Today we will see how we can implement different versions of the same service in REST terminology. Nowadays we have a boom of developing smaller services using REST and in the micro service ecosystem, those services interact with each other and produce the final business output.

Now once this ecosystem becomes big and businesses require much new functionality, sometimes we need to implement different versions of the 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 how offshore java development services providers can implement versioning in REST-based services.

What is versioning and why it is important?

In short, versioning means maintaining different endpoints of the same functionality in the service boundary layer and different consumers can consume different versions of the service based on the need. Now, it is important to understand why it is required? As a best practice, we should look for a 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 need to introduce versioning of our services, let’s think about below scenarios

  1. We have two consumer applications for the same service and we need to introduce some extra parameters 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 for versioning microservices. Before starting with 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 an application framework for exposing REST service
  2. Maven eclipse - as a standard development environment
  3. REST - as we are discussing on REST only

What we will demonstrate

Today we will create a simple REST service that 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 the 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 the same domain, we should choose one approach and stick to that approach in all the services of that project.

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

URL Versioning

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

Example URL of this could be like

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

As we can see we are 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 of the 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, especially of we have some non-technical users like Business users who want to try out the service in the browser itself, other approaches may not be accessible through a browser.

Headers Versioning

In this approach, we generally specify a custom header in the request and the 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, the 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 a 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 the same, but we pass version information through the request parameter. On 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 quickly 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 a suitable location and import that project in eclipse or your favorite IDE as a 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. Now we are ready to write our service in this project and test. For each approach of versioning, we will create a different RestController and will create two service methods for the versioning. This is very straightforward, so I will not go into the details of them in each case, this will unnecessarily increase the length of this article, so let’s see the code fragments for each approach below.

Code fragments for different versioning approach

URL Versioning

As we have already seen in this approach, we will create a separate URL with version information so that the correct method is called git. 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 the browser or any REST Client. In Local URL will be

  • http://localhost:8080/product/1
  • http://localhost:8080/product/v1/1

Headers Versioning

In this approach, we will use a 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 Parameter

In 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 the 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 a different approach than actual code. But I mentioned that versioning content can be difficult in some cases, and appropriate versioning strategies should be adopted depending on the situation and we should stick to that approach during the project to minimize complexity. Finally, introduce a 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 a POJO in a controller method, now if a different version has a different yet mostly similar Request Body then we can receive those request body in different POJO in a 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.

Recent Blogs

Categories

NSS Note

Some of our clients

team