Skip to content

OpenApi - OpenFeign Integration#

Introduction#

  • We maybe heard a lots about using OpenFeign and OpenApi in Spring Boot projects with micro-services architect. So in this section, we will try to integrate OpenFeign with OpenApi into our Spring Boot project for generating server - client apis.
  • If you are not familiar with these concepts, you should topics in links below:

Example#

  • Before we deep dive into the implementation we should go to the situations that we will need to integrate OpenApi and OpenFeign.

  • In this example, assume that we received an openapi specification from a technical analysis guy, with some apis, requests, responses and models. Then instead of writing code manually, we will use OpenApi generator plugin on Spring Boot service which will help us to generate controller and models and we will use them for building our Spring Boot service application quickly, we will name this service as openfeign-openapi-server.

  • After the code implementation on openfeign-openapi-server. Another spring boot service will need to configure spring cloud OpenFeign to use apis from openfeign-openapi-server. So instead of writing code manually, we also use the OpenApi generator plugin on Spring Boot service to generate openfeign client and models from the openapi specification of openfeign-openapi-server. Let’s call this service as openfeign-openapi-client.

  • Usually, the service openfeign-openapi-client will call to openfeign-openapi-server by url, using this way is okay but when openfeign-openapi-server changes ports or url, so we have to update openfeign-openapi-client configuration. To avoid this issue, spring cloud OpenFeign support us to use the name of target service instead of url to make connection. To do this we need an Eureka Server with registrations of openfeign-openapi-server and openfeign-openapi-client. You can view the image below for more details.

 #zoom

Eureka Server#

  • To create Eureka Server, let's add some dependencies as below.
  • Note Netflix Eureka Server dependencies require you to add Spring Cloud dependency first as mentioned in Spring Cloud Introduction. So maybe you have to add the dependency below first.
pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<properties>
    <spring.cloud-version>2021.0.0</spring.cloud-version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring.cloud-version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  • Then to use Netflix Eureka Server in your Spring Boot application. You will need to add some dependencies as below:
pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<dependencies>

    <!-- ...other dependencies -->

    <dependency>  
   <groupId>org.springframework.cloud</groupId>  
   <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>  
    </dependency>  

    <dependency>  
         <groupId>com.google.code.gson</groupId>  
         <artifactId>gson</artifactId>  
         <version>2.8.9</version>  
    </dependency>

    <!-- ...other dependencies -->

</dependencies>
  • Now, you will need to add annotation @EnableEurekaServer into your main class as below:
ServerApplication.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.eureka.server;  

import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;  

@SpringBootApplication  
@EnableEurekaServer  
public class ServerApplication {  

   public static void main(String[] args) {  
      SpringApplication.run(ServerApplication.class, args);  
   }  

}
  • Then In your application.yml. Let's add some configuration. The details of configuration are put in the comments.
application.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#server run at port 8761
server:
  port: 8761

spring:
  application:
    #application name
    name: eureka-server

eureka:
  client:
    #self register is false
    register-with-eureka: false
    #self fetch registry is false
    fetch-registry: false

OpenFeig-OpenApi-Server#

  • So for Eureka Server, you need to add some dependencies as below in your pom.xml.
pom.xml
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
 <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.3</version>
        </dependency>

        <!--Openapi Generator-->
        <dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>openapi-generator</artifactId>
            <version>5.4.0</version>
        </dependency>


        <!--Openapi UI with validation-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.13.1</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.13.1</version>
        </dependency>

        <dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>jackson-databind-nullable</artifactId>
            <version>0.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.6.5</version>
        </dependency>

        <!--database - jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>2.6.3</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.1.210</version>
            <scope>runtime</scope>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>provided</scope>
        </dependency>

        <!--spring cloud eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.9</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <version>5.4.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <inputSpec>
                                ${project.basedir}/src/main/resources/openapi/openapi-server.yml
                            </inputSpec>
                            <generatorName>spring</generatorName>
                            <apiPackage>com.springboot.cloud.openfeign.openapi.server.app.api</apiPackage>
                            <modelPackage>com.springboot.cloud.openfeign.openapi.server.app.model</modelPackage>
                            <supportingFilesToGenerate>
                                ApiUtil.java
                            </supportingFilesToGenerate>
                            <configOptions>
                                <delegatePattern>true</delegatePattern>
                                <dateLibrary>java8</dateLibrary>
                                <interfaceOnly>true</interfaceOnly>
                                <useBeanValidation>true</useBeanValidation>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
  • To view more configuration that you can use in openapi generator plugin, you can view this page.
  • In this openfeign-openapi-server we just simply read the openapi specification yaml file to generate apis and models, then we also generate openapi ui to interact with these apis without using postman.
  • You can get sample openapi specification yaml in this link

  • After you import the dependencies, then you need to add the annotation @EnableDiscoveryClient in your file main as below:

OpenFeignOpenApiServer.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.springboot.cloud.openfeign.openapi.server.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class OpenFeignOpenApiServer {

    public static void main(String[] args) {
        SpringApplication.run(OpenFeignOpenApiServer.class, args);
    }

}
  • Then in your application.yml, you will need to add some configurations for datasource, JPA, h2 database, spring cloud and eureka client as below:
application.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
server:
  port: 8081

spring:
  mandatory-file-encoding: UTF-8
  http:
    encoding:
      charset: UTF-8
      enabled: true
  datasource:
    url: jdbc:h2:file:./testdb
    username: sa
    password: password
    driver-class-name: org.h2.Driver
    platform: h2
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate.ddl-auto: update
    generate-ddl: true
    show-sql: true
  h2:
    console:
      enabled: true
      path: /h2
      settings:
        trace: false
        web-allow-others: false

  #spring cloud configuration
  application:
    name: openfeign-openapi-server
  cloud:
    discovery:
      enabled: true
    loadbalancer:
      retry:
        enable: true
        maxRetriesOnSameServiceInstance: 5
        backoff:
          maxBackoff: 5000

#connect eureka server configuration
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka
    fetch-registry: true
  • The property “eureka.client.serviceUrl.defaultZone” will be the url for register of eureka server.
  • Now let’s build your service then check generated code at target/generated-sources/openapi/src/main/java/**, then you should see your generated codes as in the image below:

 #zoom

  • Next, start your eureka-server and openfeign-openapi-server and go to http://localhost:8761/eureka/ you will see openfeign-openapi-server has been registered into eureka-server.

 #zoom

  • Then when you go to http://localhost:8081/swagger-ui/index.html you should see the openapi ui is displayed as below:

 #zoom

  • Now, to implement codes for generated api class, you need to create a controller then you will implement the generated api class and @Override methods corresponding with apis as below:
ServerController.class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.springboot.cloud.openfeign.openapi.server.app.controller;  

import com.springboot.cloud.openfeign.openapi.server.app.api.V1Api;  
import com.springboot.cloud.openfeign.openapi.server.app.model.Customer;  
import com.springboot.cloud.openfeign.openapi.server.app.model.CustomerRequest;  
import com.springboot.cloud.openfeign.openapi.server.app.service.CustomerService;  
import lombok.AllArgsConstructor;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.http.HttpStatus;  
import org.springframework.http.ResponseEntity;  
import org.springframework.web.bind.annotation.RestController;  

import java.util.List;  
import java.util.UUID;  

@RestController  
@AllArgsConstructor(onConstructor = @__(@Autowired))  
public class ServerController implements V1Api {  

    private final CustomerService customerService;

    @Override  
    public ResponseEntity<UUID> createCustomer(CustomerRequest customerRequest) {  
        return new ResponseEntity<>(this.customerService.createCustomer(customerRequest), HttpStatus.CREATED);  
    }  

    @Override  
    public ResponseEntity<List<Customer>> getCustomers() {  
        return new ResponseEntity<>(this.customerService.getCustomers(), HttpStatus.OK);  
    }  

}
  • Like other normal applications, we need to create CustomerService class to handle logic for Customer apis.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.springboot.cloud.openfeign.openapi.server.app.service;  

import com.springboot.cloud.openfeign.openapi.server.app.entity.CustomerEntity;  
import com.springboot.cloud.openfeign.openapi.server.app.entity.Gender;  
import com.springboot.cloud.openfeign.openapi.server.app.model.Customer;  
import com.springboot.cloud.openfeign.openapi.server.app.model.CustomerRequest;  
import com.springboot.cloud.openfeign.openapi.server.app.repository.CustomerRepository;  
import lombok.AllArgsConstructor;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  

import java.util.List;  
import java.util.UUID;  
import java.util.stream.Collectors;  

@Service  
@AllArgsConstructor(onConstructor = @__(@Autowired))  
public class CustomerService {  

    private final CustomerRepository customerRepository;  


    public UUID createCustomer(CustomerRequest customerRequest) {  
        CustomerEntity customerEntity = this.toCustomerEntity(new CustomerEntity(), customerRequest);  
        return this.customerRepository.save(customerEntity).getId();  
    }  

    public List<Customer> getCustomers() {  
        List<CustomerEntity> customerEntities = this.customerRepository.findAll();  
        return this.toCustomers(customerEntities);  
    }  

    private CustomerEntity toCustomerEntity(CustomerEntity customerEntity, CustomerRequest customerRequest) {  
        customerEntity.setAddress(customerRequest.getAddress());  
        customerEntity.setDob(customerRequest.getDob());  
        customerEntity.setEmail(customerRequest.getEmail());  
        customerEntity.setGender(Gender.toGender(customerRequest.getGender().getValue()));  
        customerEntity.setFullName(customerRequest.getFullName());  
        customerEntity.setPhone(customerRequest.getPhone());  
        return customerEntity;  
    }  

    private List<Customer> toCustomers(List<CustomerEntity> customerEntities) {  
        return customerEntities.stream().map(c -> this.toCustomer(c, new Customer())).collect(Collectors.toList());  
}  

private Customer toCustomer(CustomerEntity customerEntity, Customer customer) {  
        customer.setId(customerEntity.getId());  
        customer.setAddress(customerEntity.getAddress());  
        customer.setFullName(customerEntity.getFullName());  
        customer.setEmail(customerEntity.getEmail());  
        customer.setGender(Customer.GenderEnum.fromValue(customerEntity.getGender().name()));  
        customer.setPhone(customerEntity.getPhone());  
        customer.setDob(customerEntity.getDob());  
        customer.setCreatedAt(customerEntity.getCreatedAt());  
        customer.setUpdatedAt(customerEntity.getUpdatedAt());  
        return customer;  
    }  

}
  • As you can see we will handle creating and getting Customers from database, you we need to define CustomerEntity and CustomerRepository.

  • We are using JPA in this sample, so we will create a sample Customer Entity as below:

CustomerEntity.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.springboot.cloud.openfeign.openapi.server.app.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.Type;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.UUID;

@Entity
@Table(name = "customers")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CustomerEntity {

    @Id
    @GeneratedValue
    @Type(type="uuid-char")
    private UUID id;
    private String fullName;
    @Email
    @Column(unique = true)
    private String email;
    private String address;
    private String phone;
    @Enumerated(EnumType.STRING)
    private Gender gender;
    private LocalDate dob;

    private OffsetDateTime createdAt;

    private OffsetDateTime updatedAt;

    @PrePersist
    public void prePersist() {
        OffsetDateTime offsetDateTime = OffsetDateTime.now();
        this.createdAt = offsetDateTime;
        this.updatedAt = offsetDateTime;
    }

    @PreUpdate
    public void preUpdate() {
        this.updatedAt = OffsetDateTime.now();
    }
}
Gender.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.springboot.cloud.openfeign.openapi.server.app.entity;

public enum Gender {

    M, F;

    public static Gender toGender(String genderStr) {
        return Gender.valueOf(genderStr);
    }

}
  • If you havem’t know JPA before, so you can view Spring Data JPA for more details.

  • Finally we will create the CustomerRepository as below:

CustomerRepository.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package com.springboot.cloud.openfeign.openapi.server.app.repository;  

import com.springboot.cloud.openfeign.openapi.server.app.entity.CustomerEntity;  
import org.springframework.data.jpa.repository.JpaRepository;  
import org.springframework.stereotype.Repository;  

import java.util.UUID;  

@Repository  
public interface CustomerRepository extends JpaRepository<CustomerEntity, UUID> {  

}
  • So, let’s start eureka-server and openfeign-openapi-server again, and go to http://localhost:8081/swagger-ui/index.html then trigger creating customer api and getting customer api, you should see result as below:

 #zoom

 #zoom

  • So that’s all for openfeign-openapi-server, now we will move to openfeign-openapi-client.

OpenFeign-OpenApi-Client#

  • So in this openfeign-openapi-client we will use the openapi specification yaml file from openfeign-openapi-server to generate client.
  • For openfeign-openapi-client we need to add some dependencies as below:
pom.xml
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <!--spring boot starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.3</version>
        </dependency>

        <!--Openapi Generator-->
        <dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>openapi-generator</artifactId>
            <version>5.4.0</version>
        </dependency>

        <!--Openapi with validation-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.13.1</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.13.1</version>
        </dependency>

        <dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>jackson-databind-nullable</artifactId>
            <version>0.2.2</version>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>provided</scope>
        </dependency>

        <!--Spring cloud openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
            <version>3.1.0</version>
        </dependency>

        <!-- eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.9</version>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <version>5.4.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <inputSpec>
                                ${project.basedir}/src/main/resources/openapi/openapi-server.yml
                            </inputSpec>
                            <generatorName>spring</generatorName>
                            <apiPackage>com.springboot.cloud.openfeign.openapi.server.app.api</apiPackage>
                            <modelPackage>com.springboot.cloud.openfeign.openapi.server.app.model</modelPackage>
                            <supportingFilesToGenerate>
                                ApiUtil.java
                            </supportingFilesToGenerate>
                            <configOptions>
                                <library>spring-cloud</library>
                                <delegatePattern>true</delegatePattern>
                                <dateLibrary>java8</dateLibrary>
                                <interfaceOnly>true</interfaceOnly>
                                <useBeanValidation>true</useBeanValidation>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
  • We should note that, to generate openapi client side, we have to config library with value as spring-cloud in the tag configOptions.
  • To view more configuration that you can use in openapi generator plugin, you can view this page.

  • After you import the dependencies, then you need to add annotations @EnableDiscoveryClient and @EnableFeignClients in your file main as below:

OpenFeignOpenApiClient.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.springboot.cloud.openfeign.openapi.client.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class OpenFeignOpenApiClient {

    public static void main(String[] args) {
        SpringApplication.run(OpenFeignOpenApiClient.class, args);
    }

}
  • Then in your application.yml, you will need to add some configurations for openfeign server name, spring cloud, spring cloud load loadbalancer and eureka client as below:
application.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
server:
  port: 8082

openfeign:
  server:
    name: OPENFEIGN-OPENAPI-SERVER

spring:
  application:
    name: openfeign-openapi-client
  cloud:
    discovery:
      enabled: true
    loadbalancer:
      retry:
        enable: true
        maxRetriesOnSameServiceInstance: 5
        backoff:
          minBackoff: 10000
          maxBackoff: 30000
          jitter: 5000

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka
    fetch-registry: true
  • Now, let’s build your openfeign-openapi-client, then check generated code at target/generated-sources/openapi/src/main/java/** and you should see your generated codes as in the image below:

 #zoom

  • Next, to openfeign can recognize and use your generated client apis, you should create a configuration class and make some configuration as below:
FeignClientConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.springboot.cloud.openfeign.openapi.client.app.config;

import com.springboot.cloud.openfeign.openapi.server.app.api.ServerApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.FeignClientBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.springboot.cloud.openfeign.openapi.server.app")
public class FeignClientConfig {

    @Value("${openfeign.server.name}")
    private String serverName;

    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public ServerApi v1ApiFeignClient() {
        return new FeignClientBuilder(applicationContext)
                        .forType(ServerApi.class, serverName)
                        .build();
    }

}
  • So in this class we will use ApplicationContext and generated client api class ServerApi to create ServerApi feign client. At this point you will confuse that how client can connect to server, so connecting to openfeign-openapi-client will be handle apart by eureka-server. both openfeign-openapi-client and openfeign-openapi-server are connected to eureka-server, so its know exactly the application names and urls correspondingly. Thus, in spring cloud openfeign, we just use the application name of openfeign-openapi-server then we can make the connection to. Doing this way, we won’t worry about changing url or port on openfeign-openapi-server anymore.

  • Note: we should use application name with all capital letters because eureka-server will automatically capitalize all application names of clients when they connect to.

  • Finally, we will create a controller with an api, this api will use openfeign client that we configure above to get data from openfeign-openapi-server. The controller is as below:

ClientController.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.springboot.cloud.openfeign.openapi.client.app.controller;

import com.springboot.cloud.openfeign.openapi.server.app.api.ServerApi;
import com.springboot.cloud.openfeign.openapi.server.app.model.Customer;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class ClientController {

    private final ServerApi serverApi;

    @RequestMapping(method = RequestMethod.GET, path = "/v1/client/customers", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<Customer>> getCustomer() {
        return this.serverApi._getCustomers();
    }

}

Testing#

  • Let’s start 3 services, then go to http://localhost:8761/eureka to check the client connections. You should see result as below:

 #zoom

  • Now, using postman to call to the export api of openfeign-openapi-server, you will see the result as below:
curl --location --request GET 'http://localhost:8082/v1/client/customers'

 #zoom

  • So it means, all configuration has been successful, request from postman will go to openfeign-openapi-client, then openfeign-openapi-client will call to openfeign-openapi-server by application name with supports of eureka-server.

Summary#

  • The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic

  • With OpenApi Generator in Spring Boot: we can generate source code for boths server side and client side which will help use reduce writing codes, making developing application faster. Moreover, we also can generate OpenApi UI for server apis that we defined in OpenApi specification file.

  • Combining OpenFeign and spring cloud eureka server will help us using generated OpenApi client easily and avoid issues that come from server side as changing domain url and ports. Moreover, we also can keep tract services that are running/available in our system.

See Also#

Referemces#