Spring Cloud OpenFeign Example

In the previous tutorial, we created three core microservices for our simple shopping cart project.

In this tutorial, we will learn how to use the Spring Cloud OpenFeign library to make REST API calls (Synchronous communication) between multiple microservices.

Spring Cloud Open Feign Overview

Feign is a declarative web service client. It makes writing web service clients easier. 

To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. 

Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. 

Spring Cloud integrates Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced HTTP client when using Feign.

What we will Build?

We will use Spring Cloud OpenFeign to make REST API calls from order-service to product-service and payment-service.

Prerequisites

Refer to the below tutorial to create three microservices such as order-serviceproduct-service, and payment-service.

Step 1: Add Spring cloud open feign Maven dependency to order-service

Open the pom.xml file of the order-service project and add the below dependency:
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>

Make sure to add spring cloud dependencies and their version.

Here is the complete pom.xml file after adding Spring cloud open feign dependency:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.4</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.microservice</groupId>
	<artifactId>orderservice</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>orderService</name>
	<description>Order Service for Microservice</description>
	<properties>
		<java.version>11</java.version>
		<spring-cloud.version>2021.0.4</spring-cloud.version>
		<jwt.version>0.9.1</jwt.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

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

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

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

                <dependency>
                    <groupId>com.microservice</groupId>
                    <artifactId>productservice</artifactId>
                    <version>0.0.1-SNAPSHOT</version>
                    <scope>compile</scope>
               </dependency>

	</dependencies>
	<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>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

Step 2: Enable Feign Client using @EnableFeignClients

package com.microservice.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {

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

Note that @EnableFeignClients annotation enables component scanning for interfaces that declare they are Feign clients.

Step 3: Create Feign REST Client to Call Product and Payment Services

ProductService Feign Client

Let's create an interface named ProductService and add the following code:

package com.microservice.orderservice.external.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "PRODUCT-SERVICE", url = "http://localhost:8081/product")
public interface ProductService {

    @PutMapping("/reduceQuantity/{id}")
    ResponseEntity<Void> reduceQuantity(
            @PathVariable("id") long productId,
            @RequestParam long quantity
    );
}

We declare a Feign client using the @FeignClient annotation:

@FeignClient(name = "PRODUCT-SERVICE", url = "http://localhost:8081/product")

The name argument passed in the @FeignClient annotation is a mandatory, arbitrary client name, while with the URL argument, we specify the API base URL.

@FeignClient(name = "PRODUCT-SERVICE", url = "http://localhost:8081/product")

Furthermore, since this interface is a Feign client, we can use the Spring Web annotations to declare the APIs that we want to reach out to.

PaymentService Feign Client

package com.microservice.orderservice.external.client;

import com.microservice.orderservice.exception.OrderServiceCustomException;
import com.microservice.orderservice.payload.request.PaymentRequest;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@FeignClient(name = "PAYMENT-SERVICE", url = "http://localhost:8083/payment")
public interface PaymentService {

    @PostMapping
    public ResponseEntity<Long> doPayment(@RequestBody PaymentRequest paymentRequest);
}

Step 4: Change the placeOrder method to Use Feign Client

First, inject both the Feign clients:

    private final ProductService productService;

    private final PaymentService paymentService;

Next, use its methods:

// ....
productService.reduceQuantity(orderRequest.getProductId(), orderRequest.getQuantity());
// ...
paymentService.doPayment(paymentRequest);
// ...

Here is the complete code of placeOrder() method in OrderServiceImpl class:

    @Override
    public long placeOrder(OrderRequest orderRequest) {

        log.info("OrderServiceImpl | placeOrder is called");

        //Order Entity -> Save the data with Status Order Created
        //Product Service - Block Products (Reduce the Quantity)
        //Payment Service -> Payments -> Success-> COMPLETE, Else
        //CANCELLED

        log.info("OrderServiceImpl | placeOrder | Placing Order Request orderRequest : " + orderRequest.toString());

        log.info("OrderServiceImpl | placeOrder | Calling productService through FeignClient");
        productService.reduceQuantity(orderRequest.getProductId(), orderRequest.getQuantity());

        log.info("OrderServiceImpl | placeOrder | Creating Order with Status CREATED");
        Order order = Order.builder()
                .amount(orderRequest.getTotalAmount())
                .orderStatus("CREATED")
                .productId(orderRequest.getProductId())
                .orderDate(Instant.now())
                .quantity(orderRequest.getQuantity())
                .build();

        order = orderRepository.save(order);

        log.info("OrderServiceImpl | placeOrder | Calling Payment Service to complete the payment");

        PaymentRequest paymentRequest
                = PaymentRequest.builder()
                .orderId(order.getId())
                .paymentMode(orderRequest.getPaymentMode())
                .amount(orderRequest.getTotalAmount())
                .build();

        String orderStatus = null;

        try {
            paymentService.doPayment(paymentRequest);
            log.info("OrderServiceImpl | placeOrder | Payment done Successfully. Changing the Oder status to PLACED");
            orderStatus = "PLACED";
        } catch (Exception e) {
            log.error("OrderServiceImpl | placeOrder | Error occurred in payment. Changing order status to PAYMENT_FAILED");
            orderStatus = "PAYMENT_FAILED";
        }

        order.setOrderStatus(orderStatus);

        orderRepository.save(order);

        log.info("OrderServiceImpl | placeOrder | Order Places successfully with Order Id: {}", order.getId());

        return order.getId();
    }
That's it. Now run the order-service microservice and let's test.

5. Demo

Place Order REST API:

The Postman client got a successful response from the place order REST API. It means that the order-service successfully made a REST API to the product-service and payment-service.

Comments