versioning Archives - Piotr's TechBlog https://piotrminkowski.com/tag/versioning/ Java, Spring, Kotlin, microservices, Kubernetes, containers Tue, 15 Dec 2020 09:11:37 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2020/08/cropped-me-2-tr-x-1.png?fit=32%2C32&ssl=1 versioning Archives - Piotr's TechBlog https://piotrminkowski.com/tag/versioning/ 32 32 181738725 Kubernetes ConfigMap Versioning for Spring Boot Apps https://piotrminkowski.com/2020/03/23/kubernetes-configmap-versioning-for-spring-boot-apps/ https://piotrminkowski.com/2020/03/23/kubernetes-configmap-versioning-for-spring-boot-apps/#respond Mon, 23 Mar 2020 15:36:51 +0000 http://piotrminkowski.com/?p=7861 Kubernetes doesn’t provide built-in support for ConfigMap or Secret versioning. Sometimes such a feature may be useful, when we are deciding to rollback a current version of our application. In Kubernetes we are able to rollback just a version of Deployment without any additional configuration properties stored in ConfigMap or Secret. Although Kubernetes does not […]

The post Kubernetes ConfigMap Versioning for Spring Boot Apps appeared first on Piotr's TechBlog.

]]>
Kubernetes doesn’t provide built-in support for ConfigMap or Secret versioning. Sometimes such a feature may be useful, when we are deciding to rollback a current version of our application. In Kubernetes we are able to rollback just a version of Deployment without any additional configuration properties stored in ConfigMap or Secret.
Although Kubernetes does not support versioning, we may achieve it there using some third-party solutions. One of them is Spinnaker. Spinnaker is an open-source, multi-cloud continuous delivery platform for releasing software changes with high velocity and confidence created by Netflix. Configuration versioning is just one of the many features offered by that tool. For more details you may refer to its documentation site, because today I would not discuss it.
In this article I’m going to describe my custom Kubernetes ConfigMap versioning mechanism created for Spring Boot applications. I have mentioned Spinnaker, since my concept is very close to the concept used there. We are just creating dedicated ConfigMap per each new version of Deployment. My solution is based on the project Spring Cloud Kubernetes Config. It makes Kubernetes ConfigMap instances available during application bootstrapping, and allows them to be used as property sources by our Spring Boot application. For more details you may refer to my previous article about Spring Cloud Kubernetes Microservices with Spring Cloud Kubernetes.

Source Code

By default, Spring Cloud Kubernetes also does not support ConfigMap or Secret versioning. That feature is available in my forked version of this library. For accessing it go to my GitHub repository: https://github.com/piomin/spring-cloud-kubernetes.

Concept

The concept around Kubernetes ConfigMap versioning is pretty easy. Especially with Spring Cloud Kubernetes. We are creating ConfigMap that needs to be labelled with app and version. The label app contains the name of Spring Boot application, that is configured by property spring.application.name. The label version indicates a number of application version, which is fetched by the library from property info.app.version. Both these properties need to be available inside application.yml orbootstrap.yml. Here’s sample version 1.0 of ConfigMap for application api-test.

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-test-1
  labels:
    version: "1.0"
    app: api-test
data:
  application.yaml: |-
    property1: v1.0

We may maintain many versions of ConfigMap used by a given application just by changing name, labels and content. Here’s version 1.1 of ConfigMap for the same api-test application.

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-test-2
  labels:
    version: "1.1"
    app: api-test
data:
  application.yaml: |-
    property1: v1.1

By default, Spring Cloud Kubernetes Config is able to inject ConfigMap to the application based on its metadata.name. I redefined this mechanism to base on the label app. Let’s take a look at some details.

Implementation

Now the question is – how to use that feature in our application. Of course, you need to include the required library to your Maven dependencies. Remember to use my forked version Spring Cloud Kubernetes Config instead of the official one.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-config</artifactId>
</dependency>

Then the bootstrap.yaml file in your application needs to contains at least the following properties to enable ConfigMap versioning mechanism.

spring:
  application:
    name: api-test
  cloud:
    kubernetes:
      config:
        enableVersioning: true
info:
  app:
    version: 1.0

Let’s take a look on the key fragment of versioning mechanism implementation inside Spring Cloud Kubernetes Config. If it is not enabled with property spring.cloud.kubernetes.config.enableVersioning it works based on metadata.name – the same as before changes. We are also checking if property info.app.version is set inside the application settings. If not, the versioning mechanism is being disabled. Otherwise we are checking all ConfigMaps in the selected namespace, and filtering them by app and version labels.

String version = environment.getProperty("info.app.version");
LOG.info("Get Config: versioning->" + enableVersioning + ", name->" + name + ", version->" + version);
Map result = new HashMap<>();
ConfigMap map = null;
if (!enableVersioning || version == null) {
   map = StringUtils.isEmpty(namespace) ? client.configMaps().withName(name).get()
         : client.configMaps().inNamespace(namespace).withName(name).get();
} else {
   Optional optMap = StringUtils.isEmpty(namespace)
      ? client.configMaps().list().getItems().stream()
         .filter(configMap -> configMap.getMetadata().getLabels()
            .containsKey("app")
               && configMap.getMetadata().getLabels().get("app").equals(name)
               && configMap.getMetadata().getLabels().containsKey("version")
               && configMap.getMetadata().getLabels().get("version").equals(version))
            .findFirst()
      : client.configMaps().inNamespace(namespace).list().getItems().stream()
         .filter(configMap -> configMap.getMetadata().getLabels()
            .containsKey("app")
               && configMap.getMetadata().getLabels().get("app").equals(name)
               && configMap.getMetadata().getLabels().containsKey("version")
               && configMap.getMetadata().getLabels().get("version").equals(version))
            .findFirst();
   if (optMap.isPresent()) {
      map = optMap.get();
   }
}

Demo of Kubernetes ConfigMap versioning

Our sample Spring Boot application is very simple. It just exposes a single HTTP endpoint that displays the value of property injected from ConfigMap as shown below.

@RestController
public class ApiController {

   @Value("${property1}")
   private String property1;

   @GetMapping("/property")
   public String getProperty1() {
      return property1;
   }
   
}

Of course it is using the currently described versioning mechanism. First let’s run an instance of Kubernetes locally using Kind (Kubernetes IN Docker). Here’s the command that creates the local cluster.

$ kind create cluster --config=cluster.yaml

To make it available under virtual address 192.168.99.100 we need to provide the following configuration file cluster.yaml.

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  apiServerAddress: 192.168.99.100
  apiServerPort: 6443

After running the following command, kind is ready to use.

kubernetes-configmap-versioning-spring-boot-kind

Now, let’s create two test ConfigMaps for our application with kubectl apply command. Let’s take a look at the result.

kubernetes-configmap-versioning-spring-boot-configmap

Then we should deploy our sample app. The only difference between two subsequent deployments is in info.app.version property, that is 1.0 for first deployment, and 1.1 for the second deployment. Here’s the Deployment manifest. As you see below it does not inject any ConfigMap or Secret using Kubernetes mechanisms.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-test
spec:
  selector:
    matchLabels:
      app: api-test
  template:
    metadata:
      labels:
        app: api-test
    spec:
      containers:
      - name: api-test
        image: piomin/api-test
        ports:
        - containerPort: 8080

In the newest version of our sample application I just changed the value of property info.app.version from 1.0 to 1.1.

spring:
  application:
    name: api-test
  cloud:
    kubernetes:
      config:
        enableVersioning: true
info:
  app:
    version: 1.1

Let’s call our test endpoint. It returns the value of property1 taken from ConfigMap labelled with 1.1.

$ curl http://localhost:8080/property
v1.1

Here’s the history of deployments for api-test application. We may rollback the version of deployment to the previous one (1) by using command kubectl rollout undo.

kubernetes-configmap-versioning-spring-boot-rollback

Now we may call our test endpoint one more time. As you can see below it returns v1.0, which is set as a property1 value in ConfigMap labelled with version 1.0.

$ curl http://localhost:8080/property
v1.0

Conclusion

To use described versioning mechanism on Kubernetes you just need to create ConfigMap labelled with properly, include forked Spring Cloud Kubernetes Config from my repository and add property info.app.version to the Spring Boot application properties.

The post Kubernetes ConfigMap Versioning for Spring Boot Apps appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2020/03/23/kubernetes-configmap-versioning-for-spring-boot-apps/feed/ 0 7861
Microservices traffic management using Istio on Kubernetes https://piotrminkowski.com/2018/05/09/microservices-traffic-management-using-istio-on-kubernetes/ https://piotrminkowski.com/2018/05/09/microservices-traffic-management-using-istio-on-kubernetes/#respond Wed, 09 May 2018 09:56:44 +0000 https://piotrminkowski.wordpress.com/?p=6513 I have already described a simple example of route configuration between two microservices deployed on Kubernetes in one of my previous articles: Service Mesh with Istio on Kubernetes in 5 steps. You can refer to this article if you are interested in the basic information about Istio, and its deployment on Kubernetes via Minikube. Today […]

The post Microservices traffic management using Istio on Kubernetes appeared first on Piotr's TechBlog.

]]>
I have already described a simple example of route configuration between two microservices deployed on Kubernetes in one of my previous articles: Service Mesh with Istio on Kubernetes in 5 steps. You can refer to this article if you are interested in the basic information about Istio, and its deployment on Kubernetes via Minikube. Today we will create some more advanced traffic management rules based on the same sample applications as used in the previous article about Istio.

The source code of sample applications is available on GitHub in repository sample-istio-services (https://github.com/piomin/sample-istio-services.git). There are two sample applications callme-service and caller-service deployed in two different versions 1.0 and 2.0. Version 1.0 is available in branch v1 (https://github.com/piomin/sample-istio-services/tree/v1), while version 2.0 in the branch v2 (https://github.com/piomin/sample-istio-services/tree/v2). Using these sample applications in different versions I’m going to show you different strategies of traffic management depending on a HTTP header set in the incoming requests.

We may force caller-service to route all the requests to the specific version of callme-service by setting header x-version to v1 or v2. We can also not set this header in the request which results in splitting traffic between all existing versions of service. If the request comes to version v1 of caller-service the traffic is splitted 50-50 between two instances of callme-service. If the request is received by v2 instance of caller-service 75% traffic is forwarded to version v2 of callme-service, while only 25% to v1. The scenario described above has been illustrated on the following diagram.

istio-advanced-1

Before we proceed to the example, I should say some words about traffic management with Istio. If you have read my previous article about Istio, you would probably know that each rule is assigned to a destination. Rules control a process of requests routing within a service mesh. The one very important information about them,especially for the purposes of the example illustrated on the diagram above, is that multiple rules can be applied to the same destination. The priority of every rule is determined by the precedence field of the rule. There is one principle related to a value of this field: the higher value of this integer field, the greater priority of the rule. As you may probably guess, if there is more than one rule with the same precedence value the order of rules evaluation is undefined. In addition to a destination, we may also define a source of the request in order to restrict a rule only to a specific caller. If there are multiple deployments of a calling service, we can even filter them out by setting source’s label field. Of course, we can also specify the attributes of an HTTP request such as uri, scheme or headers that are used for matching a request with a defined rule.

Ok, now let’s take a look at the rule with the highest priority. Its name is callme-service-v1 (1). It applies to callme-service (2), and has the highest priority in comparison to other rules (3). It is applied only to requests sent by caller-service (4), that contain HTTP header x-version with value v1 (5). This route rule applies only to version v1 of callme-service (6).

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: callme-service-v1 # (1)
spec:
  destination:
    name: callme-service # (2)
  precedence: 4 # (3)
    match:
      source:
        name: caller-service # (4)
    request:
      headers:
        x-version:
          exact: "v1" # (5)
    route:
    - labels:
    version: v1 # (6)

Here’s the fragment of the first diagram, which is handled by this route rule.

istio-advanced-7

The next rule callme-service-v2 (1) has a lower priority (2). However, it does not conflict with the first rule, because it applies only to the requests containing x-version header with value v2 (3). It forwards all requests to version v2 of callme-service (4).

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: callme-service-v2 # (1)
spec:
  destination:
    name: callme-service
  precedence: 3 # (2)
    match:
      source:
        name: caller-service
      request:
        headers:
          x-version:
            exact: "v2" # (3)
    route:
    - labels:
    version: v2 # (4)

As before, here’s the fragment of the first diagram, which is handled by this route rule.

istio-advanced-6

The rule callme-service-v1-default (1) visible in the code fragment below has a lower priority (2) than two previously described rules. In practice it means that it is executed only if conditions defined in two previous rules were not fulfilled. Such a situation occurs if you do not pass the header x-version inside HTTP request, or it would have different value than v1 or v2. The rule visible below applies only to the instance of service labeled with v1 version (3). Finally, the traffic to callme-service is load balanced in proportions 50-50 between two versions of that service (4).

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: callme-service-v1-default # (1)
spec:
  destination:
    name: callme-service
  precedence: 2 # (2)
  match:
    source:
      name: caller-service
    labels:
      version: v1 # (3)
    route: # (4)
    - labels:
      version: v1
      weight: 50
    - labels:
      version: v2
      weight: 50

Here’s the fragment of the first diagram, which is handled by this route rule.

istio-advanced-4

The last rule is pretty similar to the previously described callme-service-v1-default. Its name is callme-service-v2-default (1), and it applies only to version v2 of caller-service (3). It has the lowest priority (2), and splits traffic between two version of callme-service in proportions 75-25 in favor of version v2 (4).

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: callme-service-v2-default # (1)
spec:
  destination:
    name: callme-service
  precedence: 1 # (2)
    match:
      source:
        name: caller-service
      labels:
        version: v2 # (3)
      route: # (4)
      - labels:
        version: v1
        weight: 25
      - labels:
        version: v2
        weight: 75

The same as before, I have also included the diagram illustrating the behaviour of this rule.

istio-advanced-5

All the rules may be placed inside a single file. In that case they should be separated with line ---. This file is available in code’s repository inside callme-service module as multi-rule.yaml. To deploy all defined rules on Kubernetes just execute the following command.

$ kubectl apply -f multi-rule.yaml

After successful deploy you may check out the list of available rules by running command istioctl get routerule.

istio-advanced-2

Before we will start any tests, we obviously need to have sample applications deployed on Kubernetes. These applications are really simple and pretty similar to the applications used for tests in my previous article about Istio. The controller visible below implements method GET /callme/ping, which prints version of application taken from pom.xml and value of x-version HTTP header received in the request.

[code language=”java”]@RestController
@RequestMapping(“/callme”)
public class CallmeController {

private static final Logger LOGGER = LoggerFactory.getLogger(CallmeController.class);

@Autowired
BuildProperties buildProperties;

@GetMapping(“/ping”)
public String ping(@RequestHeader(name = “x-version”, required = false) String version) {
LOGGER.info(“Ping: name={}, version={}, header={}”, buildProperties.getName(), buildProperties.getVersion(), version);
return buildProperties.getName() + “:” + buildProperties.getVersion() + ” with version ” + version;
}

}

Here’s the controller class that implements method GET /caller/ping. It prints a version of caller-service taken from pom.xml and calls method GET callme/ping exposed by callme-service. It needs to include x-version header to the request when sending it to the downstream service.

[code language=”java”]@RestController
@RequestMapping(“/caller”)
public class CallerController {

private static final Logger LOGGER = LoggerFactory.getLogger(CallerController.class);

@Autowired
BuildProperties buildProperties;
@Autowired
RestTemplate restTemplate;

@GetMapping(“/ping”)
public String ping(@RequestHeader(name = “x-version”, required = false) String version) {
LOGGER.info(“Ping: name={}, version={}, header={}”, buildProperties.getName(), buildProperties.getVersion(), version);
HttpHeaders headers = new HttpHeaders();
if (version != null)
headers.set(“x-version”, version);
HttpEntity entity = new HttpEntity(headers);
ResponseEntity response = restTemplate.exchange(“http://callme-service:8091/callme/ping”, HttpMethod.GET, entity, String.class);
return buildProperties.getName() + “:” + buildProperties.getVersion() + “. Calling… ” + response.getBody() + ” with header ” + version;
}

}

Now, we may proceed to applications build and deployment on Kubernetes. Here are are the further steps.

1. Building application

First, switch to branch v1 and build the whole project sample-istio-services by executing mvn clean install command.

2. Building Docker image

The Dockerfiles are placed in the root directory of every application. Build their Docker images by executing the following commands.

$ docker build -t piomin/callme-service:1.0 .
$ docker build -t piomin/caller-service:1.0 .

Alternatively, you may omit this step, because images piomin/callme-service and piomin/caller-service are available on my Docker Hub account.

3. Inject Istio components to Kubernetes deployment file

Kubernetes YAML deployment file is available in the root directory of every application as deployment.yaml. The result of the following command should be saved as a separated file, for example deployment-with-istio.yaml.

$ istioctl kube-inject -f deployment.yaml

4. Deployment on Kubernetes

Finally, you can execute a well-known kubectl command in order to deploy a Docker container with our sample application.


$ kubectl apply -f deployment-with-istio.yaml

Then switch to branch v2, and repeat the steps described above for version 2.0 of the sample applications. The final deployment result is visible in the picture below.

istio-advanced-3

One very useful thing when running Istio on Kubernetes is out-of-the-box integration with such tools like Zipkin, Grafana or Prometheus. Istio automatically sends some metrics, that are collected by Prometheus, for example total number of requests in metric istio_request_count. YAML deployment files for these plugins ara available inside directory ${ISTIO_HOME}/install/kubernetes/addons. Before installing Prometheus using kubectl command I suggest to change service type from default ClusterIP to NodePort by adding the line type: NodePort.

apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/scrape: 'true'
  labels:
    name: prometheus
  name: prometheus
  namespace: istio-system
spec:
  type: NodePort
  selector:
    app: prometheus
  ports:
  - name: prometheus
    protocol: TCP
    port: 9090

Then we should run command kubectl apply -f prometheus.yaml in order to deploy Prometheus on Kubernetes. The deployment is available inside istio-system namespace. To check the external port of service run the following command. For me, it is available under address http://192.168.99.100:32293.

istio-advanced-14

In the following diagram visualized using Prometheus I filtered out only the requests sent to callme-service. Green color points to requests received by version v2 of the service, while red color points to requests processed by version v1 of the service. Like you can see in this diagram, in the beginning I have sent the requests to caller-service with HTTP header x-version set to value v2, then I didn’t set this header and traffic has been splitted between to deployed instances of the service. Finally I set it to v1. I defined an expression rate(istio_request_count{callme-service.default.svc.cluster.local}[1m]), which returns per-second rate of requests received by callme-service.

istio-advanced-13

Testing

Before sending some test requests to caller-service we need to obtain its address on Kubernetes. After executing the following command you see that it is available under address http://192.168.99.100:32237/caller/ping.

istio-services-16

We have four possible scenarios. First, when we set header x-version to v1 the request will be always routed to callme-service-v1.

istio-advanced-10

If a header x-version is not included in the requests the traffic will be splitted between callme-service-v1

istio-advanced-11

… and callme-service-v2.

istio-advanced-12

Finally, if we set header x-version to v2 the request will be always routed to callme-service-v2.

istio-advanced-14

Conclusion

Using Istio you can easily create and apply simple and more advanced traffic management rules to the applications deployed on Kubernetes. You can also monitor metrics and traces through the integration between Istio and Zipkin, Prometheus and Grafana.

The post Microservices traffic management using Istio on Kubernetes appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2018/05/09/microservices-traffic-management-using-istio-on-kubernetes/feed/ 0 6513
Versioning REST API with Spring Boot and Swagger https://piotrminkowski.com/2018/02/19/versioning-rest-api-with-spring-boot-and-swagger/ https://piotrminkowski.com/2018/02/19/versioning-rest-api-with-spring-boot-and-swagger/#respond Mon, 19 Feb 2018 14:26:04 +0000 https://piotrminkowski.wordpress.com/?p=6330 One thing’s for sure. If you don’t have to version your API, do not try to do that. However, sometimes you have to. A large part of the most popular services like Twitter, Facebook, Netflix, or PayPal is versioning their REST APIs. The advantages and disadvantages of that approach are obvious. On the one hand, […]

The post Versioning REST API with Spring Boot and Swagger appeared first on Piotr's TechBlog.

]]>
One thing’s for sure. If you don’t have to version your API, do not try to do that. However, sometimes you have to. A large part of the most popular services like Twitter, Facebook, Netflix, or PayPal is versioning their REST APIs. The advantages and disadvantages of that approach are obvious. On the one hand, you don’t have to worry about making changes in your API even if many external clients and applications consume it. But on the other hand, you have maintained different versions of API implementation in your code, which sometimes may be troublesome.

In this article, I’m going to show you how to maintain several versions of the REST API in your application in the most comfortable way. We will base on the sample application written on the top of the Spring Boot framework and exposing API documentation using Swagger and SpringFox libraries.

Spring Boot does not provide any dedicated solutions for versioning APIs. The situation is different for SpringFox Swagger2 library, which provides grouping mechanism from version 2.8.0, which is perfect for generating documentation of versioned REST API.

I have already introduced Swagger2 together with Spring Boot application in one of my previous posts. In the article Microservices API Documentation with Swagger2 you may read how to use Swagger2 for generating API documentation for all the independent microservices and publishing it in one place – on API Gateway.

Different approaches to API versioning

There are some different ways to provide an API versioning in your application. The most popular of them are:

  1. Through an URI path – you include the version number in the URL path of the endpoint, for example /api/v1/persons
  2. Through query parameters – you pass the version number as a query parameter with specified name, for example /api/persons?version=1
  3. Through custom HTTP headers – you define a new header that contains the version number in the request
  4. Through a content negotiation – the version number is included to the “Accept” header together with accepted content type. The request with cURL would look like in the following sample: curl -H "Accept: application/vnd.piomin.v1+json" http://localhost:8080/api/persons

The decision, which of that approach implement in the application is up to you. We would discuss the advantages and disadvantages of every single approach, however it is not the main purpose of that article. The main purpose is to show you how to implement versioning in Spring Boot application and then publish the API documentation automatically using Swagger2. The sample application source code is available on GitHub (https://github.com/piomin/sample-api-versioning.git). I have implemented two of the approaches described above – in point 1 and 4.

Enabling Swagger for Spring Boot

Swagger2 can be enabled in Spring Boot application by including SpringFox library. In fact, this is the suite of java libraries used for automating the generation of machine and human readable specifications for JSON APIs written using Spring Framework. It supports such formats like swagger, RAML and jsonapi. To enable it for your application include the following Maven dependencies to the project: io.springfox:springfox-swagger-ui, io.springfox:springfox-swagger2, io.springfox:springfox-spring-web. Then you will have to annotate the main class with @EnableSwagger2 and define Docker object. Docket is a Springfox’s primary configuration mechanism for Swagger 2.0. We will discuss the details about it in the next section along with the sample for each way of versioning API.

Sample API

Our sample API is very simple. It exposes basic CRUD methods for Person entity. There are three versions of API available for external clients: 1.0, 1.1 and 1.2. In the version 1.1 I have changed the method for updating Person entity. In version 1.0 it was available under /person path, while now it is available under /person/{id} path. This is the only difference between versions 1.0 and 1.1. There is also one only difference in API between versions 1.1 and 1.2. Instead of field birthDate it returns age as integer parameter. This change affects to all the endpoints except DELETE /person/{id}. Now, let’s proceed to the implementation.

Versioning using URI path

Here’s the full implementation of URI path versioning inside Spring @RestController.

@RestController
@RequestMapping("/person")
public class PersonController {

   @Autowired
   PersonMapper mapper;
   @Autowired
   PersonRepository repository;

   @PostMapping({"/v1.0", "/v1.1"})
   public PersonOld add(@RequestBody PersonOld person) {
      return (PersonOld) repository.add(person);
   }

   @PostMapping("/v1.2")
   public PersonCurrent add(@RequestBody PersonCurrent person) {
      return mapper.map((PersonOld) repository.add(person));
   }

   @PutMapping("/v1.0")
   @Deprecated
   public PersonOld update(@RequestBody PersonOld person) {
      return (PersonOld) repository.update(person);
   }

   @PutMapping("/v1.1/{id}")
   public PersonOld update(@PathVariable("id") Long id, @RequestBody PersonOld person) {
      return (PersonOld) repository.update(person);
   }

   @PutMapping("/v1.2/{id}")
   public PersonCurrent update(@PathVariable("id") Long id, @RequestBody PersonCurrent person) {
      return mapper.map((PersonOld) repository.update(person));
   }

   @GetMapping({"/v1.0/{id}", "/v1.1/{id}"})
   public PersonOld findByIdOld(@PathVariable("id") Long id) {
      return (PersonOld) repository.findById(id);
   }

   @GetMapping("/v1.2/{id}")
   public PersonCurrent findById(@PathVariable("id") Long id) {
      return mapper.map((PersonOld) repository.findById(id));
   }

   @DeleteMapping({"/v1.0/{id}", "/v1.1/{id}", "/v1.2/{id}"})
   public void delete(@PathVariable("id") Long id) {
      repository.delete(id);
   }

}

If you would like to have three different versions available in the single generated API specification you should declare three Docket @Beans – one per single version. In this case, the swagger group concept, which has been already introduced by SpringFox, would be helpful for us. The reason this concept has been introduced is a necessity for support applications that require more than one swagger resource listing. Usually, you need more than one resource listing in order to provide different versions of the same API. We can assign a group to every Docket just by invoking groupName DSL method on it. Because different versions of the API method are implemented within the same controller, we have to distinguish them by declaring path regex matching the selected version. All other settings are standard.

@Bean
public Docket swaggerPersonApi10() {
   return new Docket(DocumentationType.SWAGGER_2)
      .groupName("person-api-1.0")
      .select()
      .apis(RequestHandlerSelectors.basePackage("pl.piomin.services.versioning.controller"))
      .paths(regex("/person/v1.0.*"))
      .build()
      .apiInfo(new ApiInfoBuilder().version("1.0").title("Person API").description("Documentation Person API v1.0").build());
}

@Bean
public Docket swaggerPersonApi11() {
   return new Docket(DocumentationType.SWAGGER_2)
      .groupName("person-api-1.1")
      .select()
      .apis(RequestHandlerSelectors.basePackage("pl.piomin.services.versioning.controller"))
      .paths(regex("/person/v1.1.*"))
      .build()
      .apiInfo(new ApiInfoBuilder().version("1.1").title("Person API").description("Documentation Person API v1.1").build());
}

@Bean
public Docket swaggerPersonApi12() {
   return new Docket(DocumentationType.SWAGGER_2)
      .groupName("person-api-1.2")
      .select()
      .apis(RequestHandlerSelectors.basePackage("pl.piomin.services.versioning.controller"))
      .paths(regex("/person/v1.2.*"))
      .build()
      .apiInfo(new ApiInfoBuilder().version("1.2").title("Person API").description("Documentation Person API v1.2").build());
}

Now, we may display Swagger UI for our API just by calling URL in the web browser path /swagger-ui.html. You can switch between all available versions of API as you can see in the picture below.

api-1
Switching between available versions of API

Specification is generated by the exact version of API. Here’s documentation for version 1.0. Because method PUT /person is annotated with @Deprecated it is crossed out on the generated HTML documentation page.

api-2
Person API 1.0 specification

If you switch to group person-api-1 you will see all the methods that contains v1.1 in the path. Along them you may recognize the current version of PUT method with {id} field in the path.

api-3
Person API 1.1 specification

When using documentation generated by Swagger you may easily call every method after expanding it. Here’s the sample of calling method PUT /person/{id} from implemented for version 1.2.

api-5
Updating Person entity by calling method PUT from 1.2 version

Versioning using Accept header

To access the implementation of versioning witt ‘Accept’ header you should switch to branch header (https://github.com/piomin/sample-api-versioning/tree/header). Here’s the full implementation of content negotiation using ‘Accept’ header versioning inside Spring @RestController.

@RestController
@RequestMapping("/person")
public class PersonController {

   @Autowired
   PersonMapper mapper;
   @Autowired
   PersonRepository repository;

   @PostMapping(produces = {"application/vnd.piomin.app-v1.0+json", "application/vnd.piomin.app-v1.1+json"})
   public PersonOld add(@RequestBody PersonOld person) {
      return (PersonOld) repository.add(person);
   }

   @PostMapping(produces = "application/vnd.piomin.app-v1.2+json")
   public PersonCurrent add(@RequestBody PersonCurrent person) {
      return mapper.map((PersonOld) repository.add(person));
   }

   @PutMapping(produces = "application/vnd.piomin.app-v1.0+json")
   @Deprecated
   public PersonOld update(@RequestBody PersonOld person) {
      return (PersonOld) repository.update(person);
   }

   @PutMapping(value = "/{id}", produces = "application/vnd.piomin.app-v1.1+json")
   public PersonOld update(@PathVariable("id") Long id, @RequestBody PersonOld person) {
      return (PersonOld) repository.update(person);
   }

   @PutMapping(value = "/{id}", produces = "application/vnd.piomin.app-v1.2+json")
   public PersonCurrent update(@PathVariable("id") Long id, @RequestBody PersonCurrent person) {
      return mapper.map((PersonOld) repository.update(person));
   }

   @GetMapping(name = "findByIdOld", value = "/{idOld}", produces = {"application/vnd.piomin.app-v1.0+json", "application/vnd.piomin.app-v1.1+json"})
   @Deprecated
   public PersonOld findByIdOld(@PathVariable("idOld") Long id) {
      return (PersonOld) repository.findById(id);
   }

   @GetMapping(name = "findById", value = "/{id}", produces = "application/vnd.piomin.app-v1.2+json")
   public PersonCurrent findById(@PathVariable("id") Long id) {
      return mapper.map((PersonOld) repository.findById(id));
   }

   @DeleteMapping(value = "/{id}", produces = {"application/vnd.piomin.app-v1.0+json", "application/vnd.piomin.app-v1.1+json", "application/vnd.piomin.app-v1.2+json"})
   public void delete(@PathVariable("id") Long id) {
      repository.delete(id);
   }

}

We still have to define three Docker @Beans, but the filtering criterias are slightly different. The simple filtering by path is not an option here. We have to crate Predicate for RequestHandler object and pass it to apis DSL method. The predicate implementation should filter every method in order to find only those which have produces field with required version number. Here’s sample Docket implementation for version 1.2.

@Bean
public Docket swaggerPersonApi12() {
   return new Docket(DocumentationType.SWAGGER_2)
      .groupName("person-api-1.2")
      .select()
      .apis(p -> {
         if (p.produces() != null) {
            for (MediaType mt : p.produces()) {
               if (mt.toString().equals("application/vnd.piomin.app-v1.2+json")) {
                  return true;
               }
            }
         }
         return false;
      })
      .build()
      .produces(Collections.singleton("application/vnd.piomin.app-v1.2+json"))
      .apiInfo(new ApiInfoBuilder().version("1.2").title("Person API").description("Documentation Person API v1.2").build());
}

As you can see in the picture below the generated methods do not have the version number in the path.

api-6
Person API 1.2 specification for a content negotiation approach

When calling method for the selected version of API the only difference is in the response’s required content type.

api-7
Updating person and setting response content type

Summary

Versioning is one of the most important concepts around HTTP APIs designing. No matter which approaches to versioning you choose you should do everything to describe your API well. This seems to be especially important in the era of microservices, where your interface may be called by many other independent applications. In this case, creating documentation in isolation from the source code could be troublesome. Swagger solves all of the described problems. It may be easily integrated with your application, supports versioning. Thanks to the SpringFox project it also can be easily customized in your Spring Boot application to meet more advanced demands.

The post Versioning REST API with Spring Boot and Swagger appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2018/02/19/versioning-rest-api-with-spring-boot-and-swagger/feed/ 0 6330