Arquitecturas basadas en Microservicios: Spring Cloud Feign

Ya hemos visto ciertas partes de los microservicios como su configuración o seguridad. Usualmente los microservicios están expuestos en una interfaz HTTP, aunque esto no es obligatorio ya que podemos encontrarnos microservicios, que por distinta necesidad, deban exponerse en otros protocolos, como por ejemplo, protocolos RCPs como Apache Thrift.

Introducción a Feign

Feign ha sido creado para facilitar la integración entre microservicios mediante la creación de clientes HTTP de forma declarativa. Esto significa que, se simplifica tanto la creación de clientes, que el desarrollador simplemente necesita anotar una interfaz para tenerlo.
Feign fue desarrollado por Netflix y usa herramientas tales como Jersey, Spring MVC y CXF para escribir sus clientes tanto REST como SOAP, destacando su integración con otras partes de Spring Cloud como:

  • Autodescubimiento: Ya sea con Eureka, Consul o cualquier otro, se pueden usar los nombres de los servicios para acceder a ellos
  • Balanceo de carga con Ribbon: En próximas entradas, veremos cómo usar Ribbon y cómo integrarlo con Feign
  • Circuit breaker con Hystrix: Haremos una pequeña introducción y veremos cómo funciona este patrón en las siguientes entradas al blog

 Ejemplo de uso

Para ver con mayor claridad el funcionamento de Feign, vamos a crear un proyecto demo que podrás encontrar aquí.

La estructura de nuestro proyecto es la siguiente:
Tenemos company service y user service. El primero guarda las compañías de nuestra aplicación, pero para saber los usuarios de cada compañía, usa user service para obtenerlo.

Lo primero que vamos a implementar es el servicio de usuario, observando en detalle al controlador del servicio de usuario:

@RestController
class UserController{
    @Autowired
    UserRepository userRepository;
    @PostConstruct
    void init(){
        userRepository.deleteAll();
        userRepository.save(User.builder().name("Rafa").company("BiGeek").build());
        userRepository.save(User.builder().name("Javi").company("BiGeek").build());
        userRepository.save(User.builder().name("Victor").company("Mirai").build());
        userRepository.save(User.builder().name("Alvaro").company("Tadaima").build());
    }
    @GetMapping("/")
    public List<User> getAll(){
        return userRepository.findAll();
    }
    @GetMapping("/{company}")
    public List<User> getByCompany(@PathVariable("company") String company){
        return userRepository.findByCompany(company);
    }
}

Ya definido el comportamiento del servicio de usuarios, vamos a crear el microservicio de company, donde en su único enpoint vamos a llamar al microservicio de usuario para obtener el listado.

Para ello definimos el cliente:

@FeignClient(name = "userservice", url = "localhost:8080")
interface UserClient {
	@GetMapping("/")
	List<User> getAll();
	@GetMapping("/{company}")
	List<User> getByCompany(@PathVariable("company") String company);
}

Como podemos ver, es una copia del controlador, con esto ya lo tendríamos. Como se puede observar, hemos incluido su URL, si estamos usando autodescubrimiento como explicamos en entradas pasadas, podremos usar el nombre del servicio.

Un ejemplo del uso del cliente:

@RestController
class CompanyController {
	@Autowired
	CompanyRepository companyRepository;
	@Autowired
	UserClient userClient;
	@PostConstruct
	void init(){
		companyRepository.deleteAll();
		companyRepository.save(Company.builder().name("BiGeek").build());
		companyRepository.save(Company.builder().name("Mirai").build());
		companyRepository.save(Company.builder().name("Tadaima").build());
	}
	@GetMapping("/")
	List<CompanyDto> getAll() {
		List<Company> allCompanies = companyRepository.findAll();
		return allCompanies
				.parallelStream()
				.map(company -> {
					CompanyDto companyDto = CompanyDto
                                                                .builder()
                                                                .id(company.getId())
                                                                .name(company.getName())
                                                                .build();
					List<User> usersByCompany = userClient.getByCompany(company.getName());
					companyDto.setUsers(usersByCompany);
					return companyDto;
				})
				.collect(Collectors.toList());
	}
}

No solo se puede usar Feign para interconexión de microservicios, también podemos usarlo para conectarnos con otras APIS de una forma sencilla.

Pero ¿cómo hacemos para controlar los errores? Lo más sencillo es implementar funciones fallback para seguir el patrón circuit breaker, que detallaremos en próximas entradas, de la siguiente forma:

@FeignClient(name = "userservice", url = "localhost:8080")
interface UserClient {
	@GetMapping("/")
	List<User> getAll();
	@GetMapping("/{company}")
	List<User> getByCompany(@PathVariable("company") String company);
	@Component
	@Slf4j
	class HystrixClientFallback implements UserClient {
		@Override
		public List<User> getAll() {
			log.error("Error getAll");
			throw new RuntimeException();
		}
		@Override
		public List<User> getByCompany(String company) {
			log.error("Error getByCompany with company {}", company);
			return Collections.emptyList();
		}
	}
}

Configuración

Feign es altamente configurable a traves de beans. Por defecto tenemos configurados los siguientes:

  • Decoder: ResponseEntityDecoder que es un envoltorio de Spring Decoder
  • Encoder: SpringEncoder
  • Logger: Slf4jLogger
  • Contract: SpringMvcContract
  • Feign.Builder: HystrixFeign.Builder
  • Client: Solo si está integrado con Ribbon

 

Estos beans se pueden sobreescribir añadiendo los nuestros propios en el contexto. También podríamos configurar los siguientes, puesto que su configuración no nos viene dada por defecto:

  • Logger.Level
  • Retryer
  • ErrorDecoder
  • Request.Options
  • Collection<RequestInterceptor>
  • SetterFactory

Feign se puede usar para cualquier proyecto aunque su arquitectura no sea basada en microservicios. Esto nos da una librería potente para poder hacer nuestros clientes REST o SOAP para cualquier integración.

Referencias

≡   Spring Cloud Feign   ≡   Feign