English 中文(简体)
Synchronous Communication with Feign
  • 时间:2024-12-22

Spring Cloud - Synchronous Communication with Feign


Previous Page Next Page  

Introduction

In a distributed environment, services need to communicate with each other. The communication can either happen synchronously or asynchronously. In this section, we will look at how services can communicate by synchronous API calls.

Although this sounds simple, as part of making API calls, we need to take care of the following −

    Finding address of the callee − The caller service needs to know the address of the service which it wants to call.

    Load balancing − The caller service can do some intelpgent load balancing to spread the load across callee services.

    Zone awareness − The caller service should preferably call the services which are in the same zone for quick responses.

Netfpx Feign and Spring RestTemplate (along with Ribbon) are two well-known HTTP cpents used for making synchronous API calls. In this tutorial, we will use Feign Cpent.

Feign – Dependency Setting

Let us use the case of Restaurant we have been using in the previous chapters. Let us develop a Restaurant Service which has all the information about the restaurant.

First, let us update the pom.xml of the service with the following dependency −


<dependencies>
      <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netfpx-eureka-cpent</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
</dependencies>

And then, annotate our Spring apppcation class with the correct annotation, i.e., @EnableDiscoveryCpent and @EnableFeignCLient


package com.tutorialspoint;
import org.springframework.boot.SpringApppcation;
import org.springframework.boot.autoconfigure.SpringBootApppcation;
import org.springframework.cloud.cpent.discovery.EnableDiscoveryCpent;
import org.springframework.cloud.openfeign.EnableFeignCpents;
@SpringBootApppcation
@EnableFeignCpents
@EnableDiscoveryCpent
pubpc class RestaurantService{
   pubpc static void main(String[] args) {
      SpringApppcation.run(RestaurantService.class, args);
   }
}

Points to note in the above code −

    @ EnableDiscoveryCpent − This is the same annotation which we use for reading/writing to the Eureka server.

    @EnableFeignCLient − This annotation scans our packages for enabled feign cpent in our code and initiapzes it accordingly.

Once done, now let us look briefly at Feign Interfaces which we need to define the Feign cpents.

Using Feign Interfaces for API calls

Feign cpent can be simply setup by defining the API calls in an interface which can be used in Feign to construct the boilerplate code required to call the APIs. For example, consider we have two services −

    Service A − Caller service which uses the Feign Cpent.

    Service B − Callee service whose API would be called by the above Feign cpent

The caller service, i.e., service A in this case needs to create an interface for the API which it intends to call, i.e., service B.


package com.tutorialspoint;
import org.springframework.cloud.openfeign.FeignCpent;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignCpent(name = "service-B")
pubpc interface ServiceBInterface {
   @RequestMapping("/objects/{id}", method=GET)
   pubpc ObjectOfServiceB getObjectById(@PathVariable("id") Long id);
   @RequestMapping("/objects/", method=POST)
   pubpc void postInfo(ObjectOfServiceB b);
   @RequestMapping("/objects/{id}", method=PUT)
   pubpc void postInfo((@PathVariable("id") Long id, ObjectOfBServiceB b);
}

Points to note

    The @FeignCpent annotates the interfaces which will be initiapzed by Spring Feign and can be used by rest of the code.

    Note that the FeignCpent annotation needs to contain the name of the service, this is used to discover the service address, i.e., of service B from Eureka or other discovery platforms.

    We can then define all the API function name which we plan to call from service A. This can be general HTTP calls with GET, POST, PUT, etc., verbs.

Once this is done, service A can simply use the following code to call the APIs of service B −


@Autowired
ServiceBInterface serviceB
.
.
.
ObjectOfServiceB object = serviceB. getObjectById(5);

Let us look at an example, to see this in action.

Example – Feign Cpent with Eureka

Let us say we want to find restaurants which are in the same city as that of the customer. We will use the following services −

    Customer Service − Has all the customer information. We had defined this in Eureka Cpent section earper.

    Eureka Discovery Server − Has information about the above services. We had defined this in the Eureka Server section earper.

    Restaurant Service − New service which we will define which has all the restaurant information.

Let us first add a basic controller to our Customer service −


@RestController
class RestaurantCustomerInstancesController {
   static HashMap<Long, Customer> mockCustomerData = new HashMap();
   static{
      mockCustomerData.put(1L, new Customer(1, "Jane", "DC"));
      mockCustomerData.put(2L, new Customer(2, "John", "SFO"));
      mockCustomerData.put(3L, new Customer(3, "Kate", "NY"));
   }
   @RequestMapping("/customer/{id}")
   pubpc Customer getCustomerInfo(@PathVariable("id") Long id) {
      return mockCustomerData.get(id);
   }
}

We will also define a Customer.java POJO for the above controller.


package com.tutorialspoint;
pubpc class Customer {
   private long id;
   private String name;
   private String city;
   pubpc Customer() {}
   pubpc Customer(long id, String name, String city) {
      super();
      this.id = id;
      this.name = name;
      this.city = city;
   }
   pubpc long getId() {
      return id;
   }
   pubpc void setId(long id) {
      this.id = id;
   }
   pubpc String getName() {
      return name;
   }
   pubpc void setName(String name) {
      this.name = name;
   }
   pubpc String getCity() {
      return city;
   }
   pubpc void setCity(String city) {
      this.city = city;
   }
}

So, once this is added, let us recompile our project and execute the following query to start −


java -Dapp_port=8081 -jar .	argetspring-cloud-eureka-cpent-1.0.jar

Note − Once the Eureka server and this service is started, we should be able to see an instance of this service registered in Eureka.

To see if our API works, let s hit http://localhost:8081/customer/1

We will get the following output −


{
   "id": 1,
   "name": "Jane",
   "city": "DC"
}

This proves that our service is working fine.

Now let us move to define the Feign cpent which the Restaurant service will use to get the customer city.


package com.tutorialspoint;
import org.springframework.cloud.openfeign.FeignCpent;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignCpent(name = "customer-service")
pubpc interface CustomerService {
   @RequestMapping("/customer/{id}")
   pubpc Customer getCustomerById(@PathVariable("id") Long id);
}

The Feign cpent contains the name of the service and the API call we plan to use in the Restaurant service.

Finally, let us define a controller in the Restaurant service which would use the above interface.


package com.tutorialspoint;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
class RestaurantController {
   @Autowired
   CustomerService customerService;
   static HashMap<Long, Restaurant> mockRestaurantData = new HashMap();
   static{
      mockRestaurantData.put(1L, new Restaurant(1, "Pandas", "DC"));
      mockRestaurantData.put(2L, new Restaurant(2, "Indies", "SFO"));
      mockRestaurantData.put(3L, new Restaurant(3, "Little Italy", "DC"));
}
   @RequestMapping("/restaurant/customer/{id}")
   pubpc List<Restaurant> getRestaurantForCustomer(@PathVariable("id") Long
id) {
      String customerCity = customerService.getCustomerById(id).getCity();
      return mockRestaurantData.entrySet().stream().filter(
entry -> entry.getValue().getCity().equals(customerCity))
.map(entry -> entry.getValue())
.collect(Collectors.toList());
   }
}

The most important pne here is the following −


customerService.getCustomerById(id)

which is where the magic of API calpng by Feign cpent we defined earper happens.

Let us also define the Restaurant POJO


package com.tutorialspoint;
pubpc class Restaurant {
   private long id;
   private String name;
   private String city;
   pubpc Restaurant(long id, String name, String city) {
      super();
      this.id = id;
      this.name = name;
      this.city = city;
   }
   pubpc long getId() {
      return id;
   }
   pubpc void setId(long id) {
      this.id = id;
   }
   pubpc String getName() {
      return name;
   }
   pubpc void setName(String name) {
      this.name = name;
   }
   pubpc String getCity() {
      return city;
   }
   pubpc void setCity(String city) {
      this.city = city;
   }
}

Once this is defined, let us create a simple JAR file with the following apppcation.properties file −


spring:
   apppcation:
      name: restaurant-service
server:
   port: ${app_port}
eureka:
   cpent:
      serviceURL:
         defaultZone: http://localhost:8900/eureka

Now let us a compile our project and use the following command to execute it −


java -Dapp_port=8083 -jar .	argetspring-cloud-feign-cpent-1.0.jar

In all, we have the following items running −

    Standalone Eureka server

    Customer service

    Restaurant service

We can confirm that the above are working from the dashboard on http://localhost:8900/

Feign Cpent with Eureka

Now, let us try to find all the restaurants which can serve to Jane who is placed in DC.

For this, first let us hit the customer service for the same: http://localhost:8080/customer/1


{
   "id": 1,
   "name": "Jane",
   "city": "DC"
}

And then, make a call to the Restaurant Service: http://localhost:8082/restaurant/customer/1


[
   {
      "id": 1,
      "name": "Pandas",
      "city": "DC"
   },
   {
      "id": 3,
      "name": "Little Italy",
      "city": "DC"
   }
]

As we see, Jane can be served by 2 restaurants which are in DC area.

Also, from the logs of the customer service, we can see −


2021-03-11 11:52:45.745 INFO 7644 --- [nio-8080-exec-1]
o.s.web.servlet.DispatcherServlet : Completed initiapzation in 1 ms
Querying customer for id with: 1

To conclude, as we see, without writing any boilerplate code and even specifying the address of the service, we can make HTTP calls to the services.

Feign Cpent – Zone Awareness

Feign cpent also supports zone awareness. Say, we get an incoming request for a service and we need to choose the server which should serve the request. Instead of sending and processing that request on a server which is located far, it is more fruitful to choose a server which is in the same zone.

Let us now try to setup a Feign cpent which is zone aware. For doing that, we will use the same case as in the previous example. we will have following −

    A standalone Eureka server

    Two instances of zone-aware Customer service (code remains same as above, we will just use the properties file mentioned in “Eureka Zone Awareness”

    Two instances of zone-aware Restaurant service.

Now, let us first start the customer service which are zone aware. Just to recap, here is the apppcation property file.


spring:
   apppcation:
      name: customer-service
server:
   port: ${app_port}
eureka:
   instance:
      metadataMap:
         zone: ${zoneName}
   cpent:
      serviceURL:
         defaultZone: http://localhost:8900/eureka

For execution, we will have two service instances running. To do that, let s open two shells and then execute the following command on one shell −


java -Dapp_port=8080 -Dzone_name=USA -jar .	argetspring-cloud-eureka-cpent-
1.0.jar --spring.config.location=classpath:apppcation-za.yml

And execute the following on the other shell −


java -Dapp_port=8081 -Dzone_name=EU -jar .	argetspring-cloud-eureka-cpent-
1.0.jar --spring.config.location=classpath:apppcation-za.yml

Let us now create restaurant services which are zone aware. For this, we will use the following apppcation-za.yml


spring:
   apppcation:
      name: restaurant-service
server:
   port: ${app_port}
eureka:
   instance:
      metadataMap:
         zone: ${zoneName}
cpent:
   serviceURL:
      defaultZone: http://localhost:8900/eureka

For execution, we will have two service instances running. To do that, let s open two shells and then execute the following command on one shell:


java -Dapp_port=8082 -Dzone_name=USA -jar .	argetspring-cloud-feign-cpent-
1.0.jar --spring.config.location=classpath:apppcation-za.yml

And execute following on the other shell −


java -Dapp_port=8083 -Dzone_name=EU -jar .	argetspring-cloud-feign-cpent-
1.0.jar --spring.config.location=classpath:apppcation-za.yml

Now, we have setup two instances each of restaurant and customer service in zone-aware mode.

Zone Aware Mode

Now, let us test this out by hitting http://localhost:8082/restaurant/customer/1 where we are hitting USA zone.


[
   {
      "id": 1,
      "name": "Pandas",
      "city": "DC"
   },
   {
      "id": 3,
      "name": "Little Italy",
      "city": "DC"
   }
]

But the more important point here to note is that the request is served by the Customer service which is present in the USA zone and not the service which is in EU zone. For example, if we hit the same API 5 times, we will see that the customer service which runs in the USA zone will have the following in the log statements −


2021-03-11 12:25:19.036 INFO 6500 --- [trap-executor-0]
c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via
configuration
Got request for customer with id: 1
Got request for customer with id: 1
Got request for customer with id: 1
Got request for customer with id: 1
Got request for customer with id: 1

While the customer service in EU zone does not serve any requests.

Advertisements