Skip to content

Spring Cloud OpenFeign Custom#

Why Do We Need To Custom OpenFeign?#

  • So If you had take an example with Spring Cloud OpenFeign Basic so you can handle business requirements that need to call to another applications. However for specific cases, you had to create your own configurations. So let's imagine that you have a spring boot application and there is a business requirement that requires you to export an api. In which, this api should response data from 2 servers based on the ISO country code because one server is deployed at VietNam and the other one is deployed at Singapore.
  • So some people can say that, they can create 2 OpenFeign interfaces and use if else or switch case for the ISO country code to get the suitable OpenFeign. Yes, actually they can handle this case like that but if later we have 10 or 20 servers deployed at 10 or 20 countries, so you have to create 10 or 20 OpenFeign interfaces and use if else or switch case to support these countries codes?
  • Actually, we just need to configure the OpenFeign a little bit and this business requirement can be solved and we also don't worry about extend supports for 10 or 20 servers deployed at 10 or 20 countries. -So, let's take an example in the following steps.

Dependencies#

  • Dependency for Spring Cloud
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 you can add this dependency below for Spring Cloud OpenFeign.
pom.xml
1
2
3
4
5
<dependency>  
   <groupId>org.springframework.cloud</groupId>  
   <artifactId>spring-cloud-starter-openfeign</artifactId>  
   <version>2.2.6.RELEASE</version>  
</dependency>
  • Because we will configure Spring Cloud OpenFeign more to handle our business, so we have to add some more 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
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
        <version>3.1.0</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-okhttp</artifactId>
        <version>10.11</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-gson</artifactId>
        <version>11.8</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-slf4j</artifactId>
        <version>10.11</version>
    </dependency>

Configuration#

  • Assume that VietNam and Singapore servers export the api GET blog/posts/{id}. So, we have to create an adapter interface for this api target as below.
AdapterServiceApi.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.springboot.cloud.bff.openfeign.lib.api;

import com.springboot.cloud.bff.openfeign.lib.model.PostResponse;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

public interface AdapterServiceApi {

    @RequestMapping(method = RequestMethod.GET, value = "blog/posts/{id}")
    PostResponse getPostById(@PathVariable(value = "id") String id);

}
  • Then, create a configuration class and use Feign.builder() to configure Feign for the adapter interface as below
FeignClientAdapterBuilder.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
package com.springboot.cloud.openfeign.lib.service;

import feign.Feign;
import feign.Logger;
import feign.codec.ErrorDecoder;
import feign.gson.GsonDecoder;
import feign.gson.GsonEncoder;
import feign.okhttp.OkHttpClient;
import feign.slf4j.Slf4jLogger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration
public class FeignClientAdapterBuilder<T> {

    @Autowired
    private Environment env;

    public T feignClientAdapterConfig(String prefix, String code, Class<T> tClass) {
        return Feign.builder()
                .client(new OkHttpClient()) //configure client
                .decoder(new GsonDecoder()) //configure decoder
                .encoder(new GsonEncoder()) //configure encoder
                .errorDecoder(new ErrorDecoder.Default())   //confiure errorDecoder
                .logger(new Slf4jLogger(tClass))     //configure logger
                .logLevel(Logger.Level.FULL)        //configure logLevel
                .contract(new SpringMvcContract()) //configure contract
                //configure target url
                .target(tClass, env.getProperty(prefix.concat(".").concat(code)));
    }

}
  • So, the default Feign configurations in Spring Cloud OpenFeign will look like in the table below.
BeanType BeanName ClassName
Decoder feignDecoder SpringDecoder
Encoder feignEncoder SpringEncoder
Logger feignLogger Slf4jLogger
MicrometerCapability micrometerCapability If feign-micrometer is on the classpath and MeterRegistry is available
CachingCapability cachingCapability If @EnableCaching annotation is used. Can be disabled via feign.cache.enabled.
Contract feignContract SpringMvcContract
Feign.Builder feignBuilder FeignCircuitBreaker.Builder
Client feignClient If Spring Cloud LoadBalancer is on the classpath, FeignBlockingLoadBalancerClient is used. If none of them is on the classpath, the default feign client is used.
  • In this example, we will change some configurations as in the following table.
BeanType BeanName ClassName
Decoder gsonDecoder SpringDecoder
Encoder gsonDecoder SpringEncoder
Logger slf4jLogger Slf4jLogger
Contract springMvcContract SpringMvcContract
Feign.Builder feignBuilder Feign.Builder
Client okHttpClient OkHttpClient
  • As you can see in the target configuration, the url will be dynamic set base on the input code of method. In this example, it will be the 2 ISO countryCode. So we need to add the target urls into application.yml as below.
application.yml
1
2
3
4
5
6
7
8
server:
  port: 9092

adapter:
  service:
    url:
      vn: http://localhost:8080
      sg: http://localhost:9090
  • So with the country code is vn or sg, It will call to the url respectively. Then if in future, there are 10 or 20 more servers with 10 or 20 new country codes, we just need to add them in this application.yml and restart our Spring Boot application.

Service#

  • Now we can use this FeignClientAdapterBuilder in any Service class by @Autowired. Let’s create a service and put codes as below
PostService.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.springboot.cloud.bff.openfeign.lib.service;  

import com.google.gson.Gson;  
import com.springboot.cloud.bff.openfeign.lib.api.AdapterServiceApi;  
import com.springboot.cloud.openfeign.lib.service.FeignClientAdapterBuilder;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  

@Service  
public class PostService {  

    @Autowired  
    private FeignClientAdapterBuilder<AdapterServiceApi> feignClientAdapterBuilder;  

    public String getPostById(String countryCode, String id) {  
        AdapterServiceApi adapterServiceApi = this.feignClientAdapterBuilder  
                        .feignClientAdapterConfig("adapter.service.url", countryCode, AdapterServiceApi.class);  
        return new Gson().toJson(adapterServiceApi.getPostById(id));  
    }  

}

Controller#

  • Finally, create a simple REST controller with an api which will call the Service above.
ApiController.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
package com.springboot.cloud.bff.openfeign.lib.controller;

import com.springboot.cloud.bff.openfeign.lib.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApiController {

    @Autowired
    private PostService postService;

    @RequestMapping(method = RequestMethod.GET, path = "/v1/country/{countryId}/application/posts/{id}", produces = {"application/json"})
    public ResponseEntity<String> getPostById(@PathVariable("countryId") String countryId, @PathVariable("id") String id) {
        return new ResponseEntity<>(postService.getPostById(countryId, id), HttpStatus.OK);
    }


}

See Also#

References#