Tuesday, 8 November 2016

Declarative REST Client Feign with Spring Boot

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.

In this example I show how to use Spring Cloud / Spring Boot application with Feign. The source code for this is as follows

https://github.com/papicella/SpringBootEmployeeFeignClient

1. Include the required maven dependency for Feign as shown below

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

2. Assuming your going to lookup a service using Service Discovery with Spring Cloud then include this dependency as well, the example below is doing this using Spring Cloud Service Discovery.


<dependency>
  <groupId>io.pivotal.spring.cloud</groupId>
  <artifactId>spring-cloud-services-starter-service-registry</artifactId>
</dependency>


See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train

3. To enable Feign we simple add the annotation @EnableFeignClients as shown below


package pas.au.scs.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class SpringBootEmployeeFeignClientApplication {

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

4. Next we have to create an interface to call our service methods. The interface methods must match the service method signatures as shown below. In this example we use Spring Cloud service discovery to find our service and invoke the right implementation method, Feign can do more then just call registered services through spring cloud service discovery BUT this example does that.

EmployeeServiceClient Interface
 
package pas.au.scs.demo.employee;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.List;

@FeignClient("SPRINGBOOT-EMPLOYEE-SERVICE")
public interface EmployeeServiceClient
{
    @RequestMapping(method = RequestMethod.GET, value = "/emps")
    List<Employee> listEmployees();
}

So what does the actual service method look like?


@RestController
public class EmployeeRest
{
    private static Log logger = LogFactory.getLog(EmployeeRest.class);
    private EmployeeRepository employeeRepository;

    @Autowired
    public EmployeeRest(EmployeeRepository employeeRepository)
    {
        this.employeeRepository = employeeRepository;
    }

    @RequestMapping(value = "/emps",
                    method = RequestMethod.GET,
                    produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Employee> listEmployees()
    {
        logger.info("REST request to get all Employees");
        List<Employee> emps = employeeRepository.findAll();

        return emps;
    }

    ..... 


5. It's important to note that the Feign client is calling a service method using Spring Cloud service discovery , the screen shot below shows how it looks inside Pivotal Cloud Foundry when we select out service registry instance and click on Manage






6. Finally we just need to call our service using the Feign client interface and do do that with Autowire as required. In this example below we use a class annotated with @Controller as shown below which then using the returned data to display the results to a web page using Thymeleaf

package pas.au.scs.demo.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import pas.au.scs.demo.employee.EmployeeServiceClient;

@Controller
public class EmployeeFeignController
{
    Logger logger = LoggerFactory.getLogger(EmployeeFeignController.class);

    @Autowired
    private EmployeeServiceClient employeeServiceClient;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String homePage(Model model) throws Exception
    {
        model.addAttribute("employees", employeeServiceClient.listEmployees());

        return "employees";
    }

}

7. The Web page "employees.html" fragment accessing the returned List of employees is as follows.

<div class="col-xs-12">
    <table id="example" class="table table-hover table-bordered table-striped table-condensed">
        <thead>
        <tr>
            <th>Id</th>
            <th>Name</th>
            <th>Job</th>
            <th>Mgr</th>
            <th>Salary</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="employee : ${employees}">
            <td th:text="${employee.id}"></td>
            <td th:text="${employee.name}"></td>
            <td th:text="${employee.job}"></td>
            <td th:text="${employee.mgr}"></td>
            <td th:text="${employee.salary}"></td>
        </tr>
        </tbody>
    </table>
</div>  

More Information

1. Spring Cloud
http://projects.spring.io/spring-cloud/

2. Declarative REST Client: Feign
http://cloud.spring.io/spring-cloud-netflix/spring-cloud-netflix.html#spring-cloud-feign

No comments: