Saturday, February 24, 2024

Spring Boot Microservice - Eureka + LoadBalancer + Feign

In the post Spring Boot Microservice - Load-Balancing With Spring Cloud LoadBalancer you have already seen how to use Spring Cloud LoadBalancer to do client side load balancing with RestTemplate and WebClient. In this post we'll see how to use Spring Cloud LoadBalancer with FeignClient (Spring Open Feign) to do client-side load balancing.

Feign Client

Feign client is a declarative REST client. You create an interface and annotate it with @FeignClient annotation to declare that this interface acts as a feign client. Feign creates a dynamic implementation of this interface at run time. Two annotations that are used with FeignClient are-

  1. @EnableFeignClients- To be used with the application class. With this annotation applied, framework scans for interfaces that declare they are feign clients.
  2. @FeignClient- To be used with the interfaces declaring that a REST client with that interface should be created, which can then be autowired into another component.

Spring Cloud LoadBalancer

With client-side load balancing, logic of load balancing is with the client itself. Client can get the list of server instances with the help of discovery and registration sever like Eureka or using ServiceInstanceListSupplier. Client can decide to which instance a particular request can be routed using the client-side load balancer library. Note that Spring cloud load balancer uses round-robin load balancing by default which chooses each instance (out of the registered instances) turn by turn.

Spring Boot microservice - Eureka, LoadBalancer and Feign

In this microservice load balancing example we'll use the example shown in this post- Spring Boot Microservice Example Using FeignClient as a base example.

In this Microservice example we'll have two services CustomerService and AccountService to cater to customer and account related functionality respectively. When customer information is retrieved it should also get the accounts associated with the specific customer. For getting the associated accounts for a customer we'll have to make a call from CustomerService to AccountService. That way we'll also see communication between Microservice.

Add maven dependency

To use FeignClient Spring Cloud OpenFeign is needed so you need to add spring-cloud-starter-openfeign to your pom.xml. That should be done in CustomerService as this is the service from where we need to communicate with AccountService.

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

In order to register Microservices as client we need to add the following dependency in pom.xml of both services.

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

You need to add spring-cloud-loadbalancer in order to create a load balanced client. If you have already added spring-cloud-starter-netflix-eureka-client dependency then you should already have spring-cloud-loadbalancer added as it is one of the compiled dependencies of eureka client.

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

Using @EnableFeignClients annotation

To make your application aware of the feign clients you need to use @EnableFeignClients annotation in the application class. Then the framework scans the interfaces that are created as feign clients.

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

@SpringBootApplication
@EnableFeignClients
@ComponentScan(basePackages = "com.netjstech.customerservice")
public class CustomerServiceApplication {

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

}

Creating interface annotated with @FeignClient annotation

With in the Customer-Service create an interface AccountFeignClient.

import java.util.List;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import com.netjstech.customerservice.dto.AccountDto;

@FeignClient("account-service")
public interface AccountFeignClient {
  @GetMapping("/account/customer/{id}")
  public List<AccountDto> getAccountsByCustomerId(@PathVariable("id") Long customerId);
}

In the @FeignClient annotation the String value ("account-service" in our example) is an arbitrary client name, which is used to create a Spring Cloud LoadBalancer client. Which means Spring Open Feign has client-side load balancing setup by default. The load-balancer client above will want to discover the physical addresses for the "account-service" service. If your application is a Eureka client then it will resolve the service in the Eureka service registry.

Changes in CustomerService implementation

Make changes in the CustomerService to use the AccountFeignClient.

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.netjstech.customerservice.dao.entity.Customer;
import com.netjstech.customerservice.dao.repository.CustomerRepository;
import com.netjstech.customerservice.dto.AccountDto;
import com.netjstech.customerservice.dto.CustomerDto;
import com.netjstech.customerservice.exception.CustomerServiceException;

@Service
public class CustomerServiceFeignImpl implements CustomerService {
  
  @Autowired
  private CustomerRepository customerRepository;
  @Autowired
  private AccountFeignClient accountFeignClient;
  @Override
  public Customer saveCustomer(Customer customer) {
    return customerRepository.save(customer);
  }

  @Override
  public CustomerDto getCustomerById(Long id) {
    System.out.println("Customer id from feign impl- " + id);
    Customer customer = customerRepository.findById(id)
         .orElseThrow(()-> new CustomerServiceException("No customer found for the given id - " + id));
    List<AccountDto> accounts = accountFeignClient.getAccountsByCustomerId(id);
    // Create CustomerDTO with both customer and account info
    CustomerDto customerDto = CustomerDto.builder()
           .id(customer.getId())
           .name(customer.getName())
           .age(customer.getAge())
           .city(customer.getCity())
           .accounts(accounts)
           .build();
    return customerDto;
  }

}

Create instances of Account Service

To check whether requests are actually getting distributed among the instances or not, we'll create two or more instances of AccountService as this is the service which is called from CustomerService.

Easy way to create instances for testing is to just change port number in the properties file (yaml file). Once an instance is started just go to application.yaml and change the port number from 8082 to 8083 and start the application again. That will create one more instance of AccountService.

You can check the same in Eureka server.

FeignClient with load balancing

Once all the services are started access http://localhost:8081/customer/1 where internally a call is made to account-service from customer-service using FeignClient.

Try to access the same URL atleast 3-4 times to check whether load balancing client is working or not. By checking the logs, you can verify that calls to AccountService are getting divided between the service running on port 8082 and the service running on port 8083.

2023-10-02T10:18:49.175+05:30  INFO 23408 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_ACCOUNT-SERVICE/LAPTOP-VID5S2ND:account-service:8082: registering service...
2023-10-02T10:18:49.211+05:30  INFO 23408 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8082 (http) with context path ''
2023-10-02T10:18:49.212+05:30  INFO 23408 --- [           main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8082
2023-10-02T11:37:35.755+05:30  INFO 23408 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2023-10-02T11:42:35.757+05:30  INFO 23408 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
id .. 1
id .. 1
2023-10-02T10:19:13.057+05:30  INFO 22992 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_ACCOUNT-SERVICE/LAPTOP-VID5S2ND:account-service:8083: registering service...
2023-10-02T10:19:13.093+05:30  INFO 22992 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8083 (http) with context path ''
2023-10-02T10:19:13.094+05:30  INFO 22992 --- [           main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8083
id .. 1
id .. 1
2023-10-02T11:47:59.741+05:30  INFO 22992 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2023-10-02T11:52:59.756+05:30  INFO 22992 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration

That's all for this topic Spring Boot Microservice - Eureka + LoadBalancer + Feign. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Spring Tutorial Page


Related Topics

  1. Spring Boot Microservices Example
  2. Spring Boot Microservice - Service Registration and Discovery With Eureka
  3. Spring Boot Microservice Example Using WebClient
  4. Spring Boot + Data JPA + MySQL REST API CRUD Example
  5. Spring Boot StandAlone (Console Based) Application Example

You may also like-

  1. Dependency Injection Using factory-method in Spring
  2. Excluding Bean From Autowiring in Spring
  3. Spring WebFlux Example Using Annotation-Based Programming - Spring Web Reactive
  4. Bean Scopes in Spring With Examples
  5. Java Lambda Expressions Interview Questions And Answers
  6. Busy Spinning in Multi-Threading
  7. String in Java Tutorial
  8. Printing Numbers in Sequence Using Threads Java Program

No comments:

Post a Comment