"Welcome To Ashok IT" "Spring Boot and MicroServices" Topic : MicroServices Communication with WebClient Date : 26/01/2025 (Session - 111) _____________________________________________________________________________________________________________________________ Yesterday Session ================= * We are talking about Microservices Communication using RestTemplate Class with following below methods 1) getForEntity() 2) exchange() Example : Customer Microservice <------------------> Address Microservice Problems with RestTemplate ========================== * It is blocking Call nature i.e.,Request Per Thread Model. -> When we made request from one MS to another MS using RestTemplate at that server will trap that request by considering as one thread and this thread will be blocked untill unless API Gives response back to client. * RestTemplate calls always be synchronous communication. * RestTemplate will be deprecated in the future release of Spring and doesn't support the Loadbalancing concept. WebClient ========== * We have another technique for Supporting Microservices communication using "WebClient". * Inorder to support with Webclient we need to add the one more starter to "CustomerMicroService" i.e.,spring-boot-starter- webflux. * As we know that WebClient class will not comes through AutoConfiguration, We need to make that Class as spring bean explictly by using @Bean annotation * WebClient will support both Synchronous & Unsynchronous behaviours. Feign Client / Open Feign: ========================= * It is also one type of Rest Client given by Netflix Organization. * Earlier Netfilx Organization used the Feign client for their Internal projects for communication between microservices. * In the last two years Netflix organization stopped the feign client Development because They moved this Project Netflix open source software Community that why name of Feign client got changed to open Feign. * Spring provided abstraction layer on open Feign under sub project of "Spring-cloud" module. * Spring cloud open Feign is an declartive type of developing Rest Client. * As programmer we just need to develop Feign clients as simple "interfaces" and we no need to worry implementation of this interfaces will be taken care Feign. * we need to add the following starter to work with Feigh client i.e.,openfeign * When working with spring cloud open feign we are using main two anotations 1) @EnableFeignClients >>>>> It will scan & identify all the Feign clients available in our project. 2) @FeignClient("name") >>>>> This annotation is used to represent interface as Feign Client. CustomerService.java ==================== package com.ashokit.services; import java.util.List; import com.ashokit.request.CustomerRequest; import com.ashokit.response.ApiResponse; import com.ashokit.response.CustomerResponse; public interface CustomerService { public CustomerResponse createCustomer(CustomerRequest customerRequest); public CustomerResponse getCustomerById(Integer customerId); public ApiResponse getCustomerAndAddressById(Integer customerId); public List getAllCustomers(); } CustomerServiceImpl.java ======================== package com.ashokit.services; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.env.Environment; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; import com.ashokit.dao.CustomerDao; import com.ashokit.entity.Customer; import com.ashokit.feign.clients.AddressClient; import com.ashokit.request.CustomerRequest; import com.ashokit.response.AddressResponse; import com.ashokit.response.ApiResponse; import com.ashokit.response.CustomerResponse; @Service public class CustomerServiceImpl implements CustomerService { @Autowired private CustomerDao customerDao; @Autowired private ModelMapper modelMapper; @Autowired private RestTemplate restTemplate; @Value("${address.service.name.url}") public String addressServiceUrl; @Autowired public WebClient webClient; //Autowiring the Feignclients @Autowired public AddressClient addressClient; @Autowired private DiscoveryClient discoveryClient; @Autowired private Environment environment; @Override public CustomerResponse createCustomer(CustomerRequest customerRequest) { //converting from CustomerRequest To Entity Class Object Customer customer = convertingFromRequestToEntity(customerRequest); //Saving Customer Information Customer savedCustomer = this.customerDao.save(customer); //Converting From Customer Entity to CustomerResponse Object return this.convertingEntityFromResponse(savedCustomer); } @Override public CustomerResponse getCustomerById(Integer customerId) { Optional customerDetails = this.customerDao.findById(customerId); if (customerDetails.isPresent()) { Customer custDetails = customerDetails.get(); CustomerResponse customerResponse = this.convertingEntityFromResponse(custDetails); //calling the AddressService AddressResponse addressResponse = callingAddressServiceWithRestTemplate(customerId); //Appending AddressResponse to CustomerResponse(1st Technique) customerResponse.setAddressResponse(addressResponse); return customerResponse; }else { //throw the exception return null; } } @Override public ApiResponse getCustomerAndAddressById(Integer customerId) { Optional customerDetails = this.customerDao.findById(customerId); if (customerDetails.isPresent()) { Customer custDetails = customerDetails.get(); CustomerResponse customerResponse = this.convertingEntityFromResponse(custDetails); //calling the AddressService //AddressResponse addressResponse = callingAddressServiceWithRestTemplate(customerId); AddressResponse addressResponse = callingAddressServiceWithWebClient(customerId); //AddressResponse addressResponse = addressClient.fetchAddressByCustomerId(customerId).getBody(); //AddressResponse addressResponse = callAddressServiceByUsingDiscoveryClient(customerId); //AddressResponse addressResponse = callAddressServiceByUsingLoadBalancer(customerId); //Appending CustomerResponse & AddressResponse to ApiResponse(2nd Technique) ApiResponse apiResponse = new ApiResponse(); //setting values apiResponse.setCustomerDetails(customerResponse); apiResponse.setAddressDetails(addressResponse); return apiResponse; }else { //throw the exception return null; } } @Override public List getAllCustomers() { //Getting All Customers Details List allCustomers = this.customerDao.findAll(); //converting from List into List List allCustomerResponse = allCustomers.stream() .map(eachCustomer -> {return this.modelMapper.map(eachCustomer, CustomerResponse.class);}) .collect(Collectors.toList()); //Writing logic for Address Microservices to get All Address and map to each customer //List allAddressResponse = callingAddressServiceForAllAddressWithRestTemplate(); //List allAddressResponse = callingAddressServiceForAllAddressWithWebClient(); List allAddressResponse = addressClient.fetchAllAddresses().getBody(); //Mapping the Address with Customer List allCustomerResponse1 = allCustomerResponse.stream() .map(eachCustomer -> { Optional add = allAddressResponse.stream() .filter(eachAddress ->{ return eachAddress.getCustomerId() == eachCustomer.getId(); }) .findAny(); //setting AddressResponse to CustomerResponse eachCustomer.setAddressResponse(add.get()); return eachCustomer; }) .collect(Collectors.toList()); //returning CustomerResponse return allCustomerResponse1; } //utility method for converting customerRequest to Entity Object private Customer convertingFromRequestToEntity(CustomerRequest customerRequest) { Customer customer = modelMapper.map(customerRequest,Customer.class); System.out.println("Customer::::" + customer); return this.modelMapper.map(customerRequest,Customer.class); } //utility method for converting customerRequest to Entity Object private CustomerResponse convertingEntityFromResponse(Customer customer) { CustomerResponse custResponse = this.modelMapper.map(customer,CustomerResponse.class); System.out.println("CustomerResponse::::" + custResponse.getCreatedDate()); return custResponse; } private AddressResponse callAddressServiceByUsingDiscoveryClient(int customerId) { //Fetching all Instances related to Address-Service List allInstances = discoveryClient.getInstances("ADDRESS-SERVICE"); //Fetching the first instance all available instances ServiceInstance currentInstance = allInstances.get(0); // String apiUrl = currentInstance.getUri()+"/api/address/customer/{customerId}"; System.out.println("API URL ::::" + apiUrl); ResponseEntity addressEntity = restTemplate.exchange(apiUrl, HttpMethod.GET,null, AddressResponse.class, customerId); //checking the API Status is 200 or not if (addressEntity.getStatusCode() == HttpStatus.OK) { //API having Response body or not if (addressEntity.hasBody()) { return addressEntity.getBody(); } } return null; } private AddressResponse callAddressServiceByUsingLoadBalancer(int customerId) { ResponseEntity addressEntity = restTemplate.exchange(addressServiceUrl+"customer/"+customerId, HttpMethod.GET,null, AddressResponse.class, customerId); //checking the API Status is 200 or not if (addressEntity.getStatusCode() == HttpStatus.OK) { //API having Response body or not if (addressEntity.hasBody()) { return addressEntity.getBody(); } } return null; } //calling the Address micro service to Fetch Address of particular Customer private AddressResponse callingAddressServiceWithRestTemplate(int customerId) { System.out.println("Address Service URL:::" + addressServiceUrl); ResponseEntity addressResponseEntity = restTemplate.getForEntity(addressServiceUrl+"customer/{customerId}", AddressResponse.class, customerId ); //By Using exchange() of RestTemplate /*ResponseEntity addressResponseEntity = restTemplate.exchange(addressServiceUrl+"{customerId}", HttpMethod.GET, null, AddressResponse.class, customerId);*/ //checking the API Status if(addressResponseEntity.getStatusCode() == HttpStatus.OK) { //ResponseBody of API if(addressResponseEntity.hasBody()) { return addressResponseEntity.getBody(); } } return null; } //calling the Address micro service to Fetch Address of particular Customer private AddressResponse callingAddressServiceWithWebClient(int customerId) { AddressResponse addressResponse = webClient.get() .uri("customer/"+customerId) .retrieve() .bodyToMono(AddressResponse.class) .block(); return addressResponse; } //calling the Address micro service to Fetch Address of particular Customer private List callingAddressServiceForAllAddressWithRestTemplate() { ResponseEntity> addressResponseEntity = restTemplate.exchange(addressServiceUrl, HttpMethod.GET, null, new ParameterizedTypeReference>(){}); //checking the API Status if(addressResponseEntity.getStatusCode() == HttpStatus.OK) { //ResponseBody of API if(addressResponseEntity.hasBody()) { return addressResponseEntity.getBody(); } } return null; } //calling the Address micro service to Fetch Address of particular Customer private List callingAddressServiceForAllAddressWithWebClient() { List allAddress = webClient.get() .accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToMono(new ParameterizedTypeReference>(){}) .block(); return allAddress; } } AddressClient.java ================== package com.ashokit.feign.clients; import java.util.List; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import com.ashokit.response.AddressResponse; @FeignClient(name="ADDRESS-SERVICE", path="/api/address") public interface AddressClient { @GetMapping(value = "/") public ResponseEntity> fetchAllAddresses(); @GetMapping(value = "/customer/{customerId}") public ResponseEntity fetchAddressByCustomerId(@PathVariable("customerId") Integer customerId); }