In this tutorial, we will learn about creating web service clients with Feign in a spring cloud application with an example for REST based HTTP calls. We will be developing 2 different microservices as customer-service and product-service. These 2 different services will register themselves to Netflix discovery server and will have Feign client integrated with customer-service. The customer-service will act as a client and invoke REST based web service calls to product-service in order to fetch product details. In this example, we will create the HTTP client that can perform all the CRUD operations involved with the product-service.
What is Feign
Feign is a declarative web service client that makes writing web service clients easier. We use the different annotations provided by the Spring framework such as Requestmapping
, @PathVariable
in a Java interface to define the abstract implementation of our actual API and the Feign internally process these annotations into a templatized request to the actual web service.
Spring Cloud provides out of the box integration with Ribbon and Eureka while using Feign. In this implementation, we will be using spring-boot 2.1.6.RELEASE and
Feign Integration with Spring Cloud
With maven, we include spring-cloud-starter-openfeign
artifact in our pom.xml file annotate the main class with the annotation @EnableFeignClients
. Once, this is done we create an interface that has the abstract implementation of our HTTP client and annotate it with @FeignClient
. Now, we can inject this anywhere where we want to invoke the REST API and Feign does the remaining part.
Below is a simple example of the interface that we will be creating in a while.
@FeignClient(name="product-service", configuration = CustomFeignConfig.class) public interface ProductClient { @GetMapping("/products") ListlistProducts(); ...
We can also override the different Feign defaults such as Decoder, Encoder, Logger, etc. in spring cloud with our custom implementations.
Building Spring Cloud Application
As discussed above, we will have a discovery server, customer-service and product-service. Let us quickly implement these.
Discovery Server Implementation
This implementation is exactly similar to my previous implementation of Spring cloud discovery server. Hence, let me put the snippets directly.
ServiceDiscoveryApplication.java@EnableEurekaServer @SpringBootApplication public class ServiceDiscoveryApplication { public static void main(String[] args) { SpringApplication.run(ServiceDiscoveryApplication.class, args); } }pom.xml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>application.yml
spring: application: name: discovery-service eureka: client: eureka-server-connect-timeout-seconds: 5 enabled: true fetch-registry: false register-with-eureka: false server: port: 8761
Product Service Implementation
Product service is a very simple implementation that has some REST endpoints exposed. Below is the controller implementation. It has GET and POST implementation. In the later section, we will be creating our Feign client for these endpoints in customer service.
@RestController @RequestMapping("/products") public class ProductController { @GetMapping public ListlistProducts(){ return DataStore.listProducts(); } @GetMapping("/{id}") private Product getProductById(@PathVariable String id){ return DataStore.listProducts().stream().filter(prd -> prd.getId().equalsIgnoreCase(id)).findFirst().get(); } @PostMapping private Product getProductById(@RequestBody Product product){ product.setId("PRD " + RandomUtils.nextInt()); return product; } @GetMapping("/customer/{custId}") public List listProductsByCustomerId(@PathVariable String custId){ return DataStore.listProducts().stream().filter(product -> product.getCustomerId().equalsIgnoreCase(custId)).collect(Collectors.toList()); } }
Below is the POJO for Product.java
public class Product { private String id; private String name; private String price; private String customerId;
Below is the util class that has some static dummy data.
DataStore.javapublic class DataStore { public static ListlistCustomers(){ List customers = new ArrayList (); for(int i = 1; i < 6; i++){ Customer customer = new Customer(); customer.setId("CUST" + i); customer.setAge(30 + i); customer.setName("customer " + i); customers.add(customer); } return customers; } public static List listProducts(){ List products = new ArrayList (); for(int i = 1; i < 6; i++){ Product product = new Product(); product.setId("PRD" + i); product.setName("Product Name " + 1); product.setPrice("USD " + 100 + i); product.setCustomerId("CUST" + i); products.add(product); } return products; } }
Customer Service Implementation
It also has a very similar implementation as product service. Let us define our controller first. The below implementation has only 1 endpoint exposed to fetch customer details. To popuate the different products specific to a customer, we need to call the product service API and for this we have Feign client implemented in the customer service. Feign client implementation is in our next section.
CustomerController.java@RequestMapping("/customers") @RestController public class CustomerController { @Autowired private ProductClient productClient; @GetMapping("/{id}") public CustomerDto getCustomerById(@PathVariable String id){ Customer customer = DataStore.listCustomers().stream().filter(cust -> cust.getId().equalsIgnoreCase(id)).findFirst().get(); ListCustomer.javaproducts = productClient.listProductsByCustomerId(id); CustomerDto dto = new CustomerDto(); BeanUtils.copyProperties(customer, dto); dto.setProducts(products); return dto; } }
public class Customer { private String id; private String name; private int age; }
CustomerDto includes product details too.
CustomerDto .javapublic class CustomerDto extends Customer { private Listproducts; }
Feign Client Implementation
Let us start our implementation of integrating Feign with spring cloud. Below is the dependency for the same.
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
Below is the Feign client implementation. At run-time, product-service
will be resolved with a look up in the discovery server. We can have the configurations defined in CustomFeignConfig.java to override the defaults of Feign client such as Decoder, Encoder, Logger, Contract, etc. For now, let us use the default implementations. The implementation has example for GET and POST request with parameterised requests.
package com.devglan.customerservice.feign.client; import com.devglan.customerservice.feign.config.CustomFeignConfig; import com.devglan.commons.Product; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import java.util.List; @FeignClient(name="product-service", configuration = CustomFeignConfig.class) public interface ProductClient { @GetMapping("/products") ListlistProducts(); @GetMapping("/products/{id}") Product getProductById(@PathVariable String id); @PostMapping("/products") Product create(@RequestBody Product product); @GetMapping("/products/customer/{custId}") List listProductsByCustomerId(@PathVariable String custId); }
Below is the application.yml for customer-service.
spring: application: name: customer-service server: port: 8090 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka
Testing the Application
The above example just works fine. Once all the services are deployed we can hit
http://localhost:8090/customers/CUST1to get below response which will include the list of products too.
Overriding Feign Defaults
As discussed above, we can override feignDecoder, feignEncoder, feignLogger, feignContract, etc with our custom implementation. To do so, we can define our custom bean definition in the config file that is included in FeignClient
annotation - CustomFeignConfig.java. Below is a sample example. This replaces the SpringMvcContract with feign.Contract.Default and adds a RequestInterceptor to the collection of RequestInterceptor.
For more overriding options, you can visit the official page here.
package com.devglan.customerservice.feign.config; import feign.Contract; import feign.auth.BasicAuthRequestInterceptor; import feign.okhttp.OkHttpClient; import org.springframework.context.annotation.Bean; public class CustomFeignConfig { @Bean public Contract feignContract() { return new feign.Contract.Default(); } @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor("user", "password"); } @Bean public OkHttpClient client() { return new OkHttpClient(); } }
Conclusion
In this article, we created integrated Feign client with spring cloud and developed an example application using discovery server to demonstrate the same. The source code can be found here on github.