Clients dinâmicos com Feign e Eureka

O Feign é uma boa alternativa que o Netflix o criou com o propósito de implementar clients HTTPs em Java de forma fácil.

Feign proporciona implementações com base em anotações, assim os mapeamentos para os clients HTTP são podem ser desenvolvidos baseados em interfaces, como demonstrado no exemplo abaixo:

import feign.Feign;
import feign.Param;
import feign.RequestLine;
import feign.jackson.JacksonDecoder;

public class SituationsWithoutSpring {

    public static boolean check(final String login) {
        final SituationClient client = Feign.builder()
                .decoder(new JacksonDecoder())
                .target(SituationClient.class, "http://localhost:8070/situations");

        return client.check(login);
    }
}

interface SituationClient {

    @RequestLine("GET /check/{login}")
    boolean check(@Param("login") String login);

}
  • Feign.builder(): Instância o client do Feign através de um builder;
  • decoder(new JacksonDecoder()): Define o framework Jackson como deserializador do retorno;
  • target: Define as configurações do serviço a requisitado, no caso passando a SituationClient: que a interface que mapeia a chamada no método check e a URL do serviço;
  • @RequestLine: Define o método check como uma chamada GET no destino configurado no target.

Observação: A interface utilizada para mapear os métodos, que são chamadas pré definidas a um serviço de destino, podem ter um ou mais serviços. Com isso, a idéia geral do Feigh é criar uma interface centralizada de acesso a um determinado client, que pode ter um ou vários método a serem consumidos.

Com Spring Boot/Spring MVC se torna ainda mais simples a criação dos clients, onde há uma grande integração com o Spring MVC, assim possibilitando a utilização de anotações já conhecidas como o @RequestMapping para fazer o mapeamento dos serviços, e também disponibiliza novas anotações @FeignClient para centralizar as configurações de acesso ao serviço.

@FeignClient(url = "http://localhost:8070", path = "/situations", name = "situations")
public interface SituationsWithoutEureka {

    @RequestMapping(value = "/check/{login}", method = RequestMethod.GET)
    boolean check(@PathVariable("login") String login);
}
  • @FeignClient: Registra no contexto a interface como um client do Feigh;
  • url: Define a URL do serviço a ser requisitado;
  • path: Define o path do serviço, que pode ser configurado separadamente da url;
  • name: Define um nome para o client;
  • @RequestMapping: Define o mapeamento do serviço utilizando os padrões do Spring MVC.

Além disso, quando utilizado com o conjunto de soluções Spring Cloud/Netflix, é possível construir a comunicação com serviços de forma dinâmica, ou seja, sem a nescidade de URLs ou IPs fixos dos serviços a serem requisitos na aplicações, tudo isso realizável utilizando o service discovery Eureka para registrar e resolver os serviços e esse é o cenário que será apresentando no exemplo abaixo.

Exemplo:

O exemplo é basicamente uma API para validar o acesso para um login, onde a aplicação access tem como objetivo disponibilizar um serviço HTTP GET de realiza a checagem do texto informado por parâmetro, porém ela precisa consultar a situação do login em outra aplicação (situations), dessa forma, a aplicação access necessita fazer uma chamada HTTP para consultar a aplicação situations.

Para implementar esse cenário sem a necessidade de configurar URL ou IP entre a integração das duas aplicações (acess e situations), vamos usufruir do recurso de service discovery provido pelo Eureka para registar as aplicações através de nomes e com os recursos integrados do Feigh e Spring Cloud as chamadas entre as aplicações serão realizadas através desses nomes configurados para as aplicações e resolvidos pelo Eureka, assim abstraindo a complexidade de rede e deixando dinâmica as chamadas entre as aplicações, como demostrado na imagem abaixo.

Eureka

Iniciando pela infraestrutura, o Eureka faz a função de service discovery, que a solução que provê os acesso aos serviços de forma dinâmica, onde as aplicações são registradas e são acionadas através de nomes configurados.

A configuração do Eureka é a padrão, configurando uma aplicação Spring Boot como Eureka server com os passos abaixo:

Adicionar a dependência do Eureka Server no projeto.


    
        org.springframework.cloud
        spring-cloud-netflix-eureka-server
        1.4.4.RELEASE
    

Configurar a porta de acesso ao dashboard web e desativando os auto registros.

server.port=8761

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

Habilitar o server com a anotação @EnableEurekaServer para inicializar o Eureka no startup da aplicação.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaAppConfig {

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

}

Serviço para consumo (situations)

Para fim de testes, vamos criar uma aplicação com o propósito de disponibilizar um serviço GET que um boolean de acordo com a chamada.

Como as aplicações precisam estar registras no Eureka para realizar o roteamento dos clients, é preciso registar a aplicação no service discovery com a anotação @EnableDiscoveryClient.

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

@SpringBootApplication
@EnableDiscoveryClient
public class SituationsAppConfig {

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

}

A propriedade que registra a aplicação no Eureka é a: spring.application.name, que precisa ser um nome único por aplicação para que o service discovery consiga identificar e encontrar os serviços.

spring.application.name=situations
eureka.instance.hostname=localhost
server.port=8070

Por fim, implementamos um simples do serviço no método GET com o caminho /situations/check recebendo o parâmetro login do tipo texto.

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Random;

@RestController
@RequestMapping("/situations")
public class SituationController {

    @GetMapping(path = "/check/{login}")
    public boolean check(@PathVariable("login") String login) {
        return new Random().nextBoolean();
    }

}

Client

Após a estrutura construída com o service discovery (Eureka) e a aplicação disponibilizando um serviço para consumo (situations), o cenário está pronto para criarmos a integração entre as aplicações utilizando Feigh.

A aplicação que irá consumir o serviço também precisa ser registrada no Eureka, pois ela precisa estar conectada no service discovery para consultar os endereços de serviços disponíveis.

Para que os clients Feigh sejam ativados através de anotações, é preciso habilitar coma configuração @EnableFeignClients, que ativa a criação de clients através de anotações nas interfaces e permite injeção de dependências nelas, assim tornado os clients reaproveitáveis.

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 AccessAppConfig {

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

}

A anotação que permite esse reaproveitamento é @FeignClient, que injeta o client no contexto do Spring, porém diferente do exemplo no início do post vamos utilizar a propriedade value ao invés da URL, dessa forma, vamos configurar o nome da aplicação registrada no Eureka ao invés de configurar seu host/endereço fixo, assim provendo maior flexibilidade nos clientes e deixando a cargo do service discovery o resolução de host/IPs.

@FeignClient(value = "situations", path = "/situations")
public interface Situations {

    @RequestMapping(value = "/check/{login}", method = RequestMethod.GET)
    boolean check(@PathVariable("login") String login);

}

Obs: Quando as chamadas dos clients são realizadas através do nome das aplicações, ou seja, quando utilizam o Eureka para encontrar o destino, automaticamente é acionado o Ribbon para fazer o papel de balanceador de carga e encontrando o serviço disponível no Eureka, mais detalhes podem ser visto na doc do Spring Cloud.

Encerrando o exemplo, basta injetar a interface Situations, que é um client do Feigh que pode ter um ou mais métodos já pre configurados, e utilizar de forma reaproveitável nas nossas aplicações.

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/access")
public class AccessController {

    @Autowired
    private Situations situations;

    @GetMapping("/{login}")
    public String access(@PathVariable("login") final String login) {
        return situations.check(login) ? "Valid!" : "Invalid!";
    }

}

Concluindo

Essa abordagem proporciona o benefício de realizar chamadas entre serviços através de nomes, o que proporciona dinamismos entre serviços, onde não é preciso conhecer URLs, portas, endereços, entre outras terminologias de rede que as aplicações estão configuradas e assim deixando a responsabilidade de endereçamento para service discovery(Eureka), além disso, também já prepara as chamadas para utilizarem balanceador de carga, pois um serviço pode ter mais de uma instância. Em contra partida desses benefícios, há uma dependência do papel do service discovery, que caso passe por indisponibilidade pode afetar toda interação entre os serviços.

O código fonte do exemplo está disponível no github.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.