Feign uma forma simples para consumir serviços

Dificilmente uma aplicação “vive sozinha” e em algum momento pode surgir a necessidade de integrá-la com outras aplicações.

As necessidades podem ser diversas:

  • Consultar dados de um sistema legado
  • Retornar endereço a partir de um CEP
  • Efetuar pagamento online
  • Consultar o custo de envio de determinada mercadoria
  • Fazer autenticação através de alguma rede social ou qualquer

Hoje em dia muitas das integrações utilizam o modelo arquitetural REST, e não é uma tarefa muito difícil criar um cliente para consumir um serviço web.

Vamos usar como exemplo via cep, que é uma alternativa para pesquisar um endereço a partir de um CEP.

Para consumir o serviço este basta efetuar uma requisição GET no seguinte formato https://viacep.com.br/ws/NUMERO-DO-CEP/FORMATO. O formato pode ser: json,xml,piped,querty.

Se por exemplo efetuarmos uma requisição GET para https://viacep.com.br/ws/04101300/json, temos o seguinte retorno:

{
"cep": "04101-300",
"logradouro": "Rua Vergueiro",
"complemento": "de 2771 a 5049 – lado ímpar",
"bairro": "Vila Mariana",
"localidade": "São Paulo",
"uf": "SP",
"unidade": "",
"ibge": "3550308",
"gia": "1004"
}

Consumindo Serviço com RestTemplate

Para consumir um Web Service  no Spring podemos usar a classe RestTemplate que tem uma interface de uso bem simples.

Vamos criar uma aplicação com Spring Boot para consumir o serviço do via cep.

Vamos começar adicionando o parent do Spring Boot ao pom.xml:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
    </parent>

E também o plugin para gerar o JAR auto-contido:

<build>
    <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Para representar o retorno do Web Service vamos criar a classe Endereco

public class Endereco {

    private String cep;
    private String logradouro;
    private String complemento;
    private String bairro;
    private String localidade;
    private String uf;

    //getters 

    @Override
    public String toString() {
        return "Endereco{" +
                "cep='" + cep + '\'' +
                ", logradouro='" + logradouro + '\'' +
                ", complemento='" + complemento + '\'' +
                ", bairro='" + bairro + '\'' +
                ", localidade='" + localidade + '\'' +
                ", uf='" + uf + '\'' +
                '}';
    }
}

Agora vamos criar a classe que inicializa o contexto do Spring:

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

Vamos encapsular o código para consumir o serviço em uma classe ViaCepCleient


@Component
public class ViaCEPClient {

    public Endereco buscaEnderecoPor(String cep){
        RestTemplate template = new RestRemplate();
        return template.getForObject("https://viacep.com.br/ws/{cep}/json",Endereco.class, cep);
    }
}

Como a ideia do projeto é bem simples vamos rodar tudo a partir da classe Application. A ideia é pegar o cep na variável args e através do cliente (ViaCepClient) retornar o endereço e logar no console.

O Spring Boot disponibiliza uma interface chamada CommandLineRunner que tem a declaração do método void run(String… args) throws Exception;.

Quando temos no nosso classpath algum Bean que produza CommandLineRunner, o Spring Boot após a inicialização pega o que foi recebido no método main e repassa para esse Bean.

Dessa forma conseguimos separar o código de inicialização e o código que deve ser executado baseado nos argumentos que foi passado para o Spring Boot.

Vamos alterar nossa classe Application para que ela tenha um Bean para CommandLineRunner.

@SpringBootApplication
public class Application {

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

    @Bean
    public CommandLineRunner run(ViaCEPClient client){
        return args -> {
            if (args.length > 0) {
                String cep = args[0];

                Endereco endereco = client
                                     .buscaEnderecoPor(cep);

                System.out.println(endereco);
            }
        };
    }

}

Ao executarmos nossa classe Application com algum argumento veremos no console algo similar a isso:

Endereco{cep=’04101-300′, logradouro=’Rua Vergueiro’, complemento=’de 2771 a 5049 – lado ímpar’, bairro=’Vila Mariana’, localidade=’São Paulo’, uf=’SP’}

Consumindo serviço com Feign

Agora imagine em uma aplicação onde temos que consumir diversos serviços, ou em um arquitetura de micro serviços onde temos que nos integrar com diversos serviços para que a aplicação como um todo funcione. Pense em quantas vezes vamos ter que escrever um cliente usando o RestTemplate?

E geralmente se o programador teve que repetir uma tarefa mais de 3 vezes, ele automatiza essa tarefa de alguma forma.

Pensando nisso alguém teve a brilhante ideia de deixar ainda mais fácil a criação de clientes para consumir serviços. E então nasceu o projeto Feign.

O projeto Feign foi inspirado em: Retrofit, JAXRS-2.0, entre outras. Com ele podemos criar clientes de uma forma declarativa.

Então a galera do Spring incorporou o Feign dentro da sua própria stack. Mais especificamente abaixo do projeto Spring Cloud. Que é um projeto voltado para arquitetura de micro serviços, cloud e etc.

Vamos então migrar nosso cliente de RestTemplate para usar Feign. Primeiramente vamos adicionar as dependências do projeto Spring Cloud e do Feign.

O Spring Cloud segue a mesma linha do Spring Boot com as ideias dos Starters. Só qua ao invés de usar um projeto base (parent) ele usa o conceito de dependências gerenciadas.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Dalston.SR1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Agora que temos o Spring Cloud no nosso projeto vamos adicionar a dependência do Feign

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

Para habilitar o Feign no projeto vamos anotar a classe Application com @EnableFeignClients

@SpringBootApplication
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }


    @Bean
    public CommandLineRunner run(ViaCEPClient client){
        return args -> {
            if (args.length > 0) {
                String cep = args[0];

                Endereco endereco = client.buscaEnderecoPor(cep);

                System.out.println(endereco);
            }
        };
    }
}

Como falei anteriormente com o Feign podemos criar clientes de uma forma declarativa. Ou seja só iremos declarar o cliente sem colocar nenhuma implementação.

Vamos começar convertendo nossa classe ViaCEPClient para uma interface (pois não queremos ter nenhuma implementação nela).

@Component
public interface ViaCEPClient {
    Endereco buscaEnderecoPor(String cep);
}

Vamos trocar a anotação @Component para @FeignClient e nela adicionar a URL do serviço.

@FeignClient(url="https://viacep.com.br/ws/")
public interface ViaCEPClient {
    Endereco buscaEnderecoPor(String cep);
}

Precisamos dizer qual o Endpoint o método Endereco buscaEnderecoPor(String cep); deve acessar.

Para isso vamos usar as anotações de mapeamento do Spring MVC:

  • @RequestMapping
  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping

Da mesma forma precisamos mapear os parâmetros:

    • @PathVariable
    • @RequestParam

.

@FeignClient(url="https://viacep.com.br/ws/")
public interface ViaCEPClient {

    @GetMapping("{cep}/json")
    Endereco buscaEnderecoPor(@PathVariable("cep") String cep);
}

Por ultimo o Feign pede que além da URL o nosso cliente de serviço tenha um nome (que nada mais é do que um identificador). Vamos adicionar o parâmetro name na anotação @FeignClient.

@FeignClient(url="https://viacep.com.br/ws/", name = "viacep")
public interface ViaCEPClient {

    @GetMapping("{cep}/json")
    Endereco buscaEnderecoPor(@PathVariable("cep") String cep);
}

Se rodarmos o código novamente teremos o mesmo resultado utilizando o RestTemplate. Com a diferença que o código real para consumir o serviço será gerado em tempo de execução. Ou seja nunca mais precisamos usar RestTemplate para gerar clientes.

E aí o que achou do Feign?

Spring Data Rest é vida

Fala aí galera, tudo bem? Meu nome é Fernando e sou instrutor e desenvolvedor na caelumalura.

Não é de hoje que o Spring vem facilitando nossa vida, e vamos combinar que os caras mandam muito bem!

Agora imagina só um mundo perfeito, onde declaramos nossas classes de modelo, repositórios e magicamente temos enpoints para fazermos todas as operações que declaramos nos nossos repositórios (por exemplo incluir, listar, alterar e remover).

Esse mundo perfeito existe e nele essa mágica se chama Spring data rest. Sim, ao utilizarmos ele só de declararmos nossos repositórios temos endpoints disponíveis.

Tá, legal saber que existe o Spring Data Rest.  Mas e como uso ele? (Show me the code!!!)

Bom galera eu montei um exemplo bem básico com Spring Boot Maven e está no meu Github. Nele você vai ver no arquivo pom.xml as seguintes declarações:

Spring Boot

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>1.4.2.RELEASE</version>
</parent>

E os staters do Spring Data e do Spring data Rest

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Além dos Starters tenho também o h2 como banco de dados em memória e o lombok.

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
</dependency>

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>

Para quem não conheçe o lombok, ele nos ajuda a escrever menos código repetitivo por exemplo: getter, setter, construtor, toString, equals/hashcode e etc...

Com ele anotamos nossos atributos com @Getter e/ou @Setter, anotamos nossa classe com @NoArgsConstructor ou @RequiredArgsConstructor, @AllArgsConstructor e ao compilarmos nosso código ele gera os byte-codes equivalentes.Vale a pena dar uma conferida!

No meu caso estou usando a anotação @Data (que engloba um monte dessas anotações) e @NoArgsConstructor (para ter um construtor sem parâmetros, por conta dos frameworks).

(OBS: Para rodar o projeto por dentro do IDE você precisa instalar o agent lombok, do contrário seu IDE não vai encontrar o getters, setters e afins.)

Em java temos duas classes de modelo Book e Author.

@Entity
@Data
@NoArgsConstructor
public class Author {

	@Id
	@GeneratedValue
	private Long id;

	@NotBlank
	private String name;

}
@Entity
@Data
@NoArgsConstructor
public class Book {

	@Id
	@GeneratedValue
	private Long id;

	@NotBlank
	private String title;

	@NotBlank
	private String description;

	@Min(50)
	private Integer numOfPages;

	@Valid
	@ManyToMany(fetch=FetchType.EAGER)
	private Set<Author> authors = new HashSet<>();

	public void add(Author author) {
		authors.add(author);
	}

}

Além do modelo tenho também um repositórios para cada, BookRepository e AuthorRepository

public interface BookRepository extends CrudRepository<Book, Long> {
}
public interface AuthorRepository extends CrudRepository<Author, Long>{
}

Para subir a aplicação tenho a classe Application que inicia o Spring Boot e salva um livro e um autor no banco de dados;

@SpringBootApplication
public class Application {

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

	@Autowired
	private BookRepository bookRepository;

	@Autowired
	private AuthorRepository authorRepository;

	@PostConstruct
	@Transactional
	public void onLoad(){

		Author alberto = new Author();
		alberto.setName("Alberto");
		authorRepository.save(alberto);

		Book book = new Book();
		book.setTitle("Spring MVC");
		book.setDescription("Domine o principal framework web Java");
		book.setNumOfPages(237);

		book.add(alberto);

		bookRepository.save(book);

	}

}

E pronto! Temos uma endpoints para efetuarmos o CRUD de livros e autores. (Spring Data Rest é ou não é vida?)

Ao acessarmos http://localhost:8080, temos o seguinte retorno

{
"_links" : {
"authors" : {
"href" : "http://localhost:8080/authors&quot;
},
"books" : {
"href" : "http://localhost:8080/books&quot;
},
"profile" : {
"href" : "http://localhost:8080/profile&quot;
}
}
}

O Spring Data Rest usa fortemente os conceitos de HATEOAS que a grosso modo diz que através de um recurso disponível deve ser possível navegar pelo estado desse recurso através dos links. Por padrão o spring usa um modelo de apresentação chamado HAL para representação da resposta.

Bom os endpoints /authors e /books me parecem bem ok, o que deve ter neles, mas e o endpoint /profile?

Esse enpoint tem as meta-informações do seus serviços, por exemplo ao acessarmos http://localhost:8080/profile/authors temos o seguinte resultado

{
"alps":{
"version":"1.0",
"descriptors":[
{
"id":"author-representation",
"href":"http://localhost:8080/profile/authors&quot;,
"descriptors":[
{
"name":"name",
"type":"SEMANTIC"
}
]
},
{
"id":"create-authors",
"name":"authors",
"type":"UNSAFE",
"rt":"#author-representation"
},
{
"id":"get-authors",
"name":"authors",
"type":"SAFE",
"rt":"#author-representation"
},
{
"id":"patch-author",
"name":"author",
"type":"UNSAFE",
"rt":"#author-representation"
},
{
"id":"update-author",
"name":"author",
"type":"IDEMPOTENT",
"rt":"#author-representation"
},
{
"id":"get-author",
"name":"author",
"type":"SAFE",
"rt":"#author-representation"
},
{
"id":"delete-author",
"name":"author",
"type":"IDEMPOTENT",
"rt":"#author-representation"
}
]
}
}

Essas meta-informações estão associadas a qual representação temos no enpoint (no nosso caso)  authors e toda a definição dessa representação (os atributos e métodos). O endpoint profile é definido na RFC 6906.

Dentro da representação do author temos um objeto com id author-representation e nele temos um descritor com nome name e o tipo dele é SEMANTIC.
Esse tipo SEMANTIC indica um elemento que guarda um estado (atributo).

Além desse objeto temos um objeto com id create-authors, get-authors ou update-author ambos com nome author porém com tipos diferentes (nesse caso temos UNSAFE, SAFE e IDEMPOTENT). Esse objetos são as operações que podemos fazer sobre essa representação (methods).

Cada tipo tem um comportamento diferente:

SAFE – operações desse tipo são idempotentes e não alteram estado no servidor (GET, HEAD).

UNSAFE – operações desse tipo não são idempotentes e alteram estado no servidor (POST).

IDEMPOTENT – operações desse tipo são idempotentes e alteram estado no servidor (PUT, DELETE).

Uma operação é considerada idempotente se o resultado dele for bem sucedida indiferente do numero de vezes que ela for executada.

Agora sim, vamos para os endpoints mais “óbvieis“. Irei utilizar o programa curl para efetuar as chamadas para nosso endpoints.

Para listar nosso autores podemos efetuar a seguinte requisição:

curl -X GET http://localhost:8080/authors

(Deixei explicito o verbo GET somente por questões didáticas).

Nesse caso temos o seguinte resultado:

{
"_embedded" : {
"authors" : [ {
"name" : "Alberto",
"_links" : {
"self" : {
"href" : "http://localhost:8080/authors/1&quot;
},
"author" : {
"href" : "http://localhost:8080/authors/1&quot;
}
}]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/authors&quot;
},
"profile" : {
"href" : "http://localhost:8080/profile/authors&quot;
}
}
}

Nesse caso temos um autor com o nome Alberto e os links do autor levam ao recurso dele mesmo. E os links da representação como um todo levam a lista de autores ou ao profile de autores.

Vamos seguirmos o link para listar somente o autor Alberto.

curl -X GET http://localhost:8080/authors/1

E nesse caso temos o seguinte resultado:

{
"name" : "Alberto",
"_links" : {
"self" : {
"href" : "http://localhost:8080/authors/1&quot;
},
"author" : {
"href" : "http://localhost:8080/authors/1&quot;
}
}
}

E para criar um novo autor? bom para isso temos que usar o verbo POST.

curl -X POST -H "Content-type: application/json" -d '{"name": "Fernando"}' http://localhost:8080/authors

Agora temos dois autores, e para alterar? Fácil né, só usar outro verbo. Nesse caso o PATCH

 curl -X PATCH -H "Content-type: application/json" -d '{"name": "Fernando Willian"}' http://localhost:8080/authors/2

E para excluir vamos usar o verbo DELETE

curl -X DELETE http://localhost:8080/authors/2

Podemos usar as mesmas operações para os livros.

Para deixar um pouco mais interessante, vamos adicionar um pouco de segurança ao nossos endpoints.
Para isso vamos adicionar o starter do Spring Security no nosso arquivo pom.xml:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

E vamos configurar como será nossa autenticação e quais os endereços que queremos proteger:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
		.withUser("common").password("123").roles("USER").and()
		.withUser("admin").password("@dm1n").roles("ADMIN");
		
	}
	
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.httpBasic()
			.and()
				.authorizeRequests()
					.antMatchers(HttpMethod.POST, "/**").hasRole("ADMIN")
					.antMatchers(HttpMethod.PATCH, "/**").hasRole("ADMIN")
					.antMatchers(HttpMethod.DELETE, "/**").hasRole("ADMIN")
				.and()
					.csrf()
						.disable();
	}
	
}

Nessa configuração temos que nos autenticar com uma conta com permissão de ADMIN para efetuar qualquer requisição com os verbos POST, PATCH e DELETE.

Agora para incluir um novo autor devemos nos autenticar:

curl -X POST -H "Content-type: application/json" -d '{"name": "Fernando"}' http://localhost:8080/authors -u admin:@dm1n

O mesmo se aplica para alteração ou exclusão.

E aí o que achou do Spring Data Rest? Já o conhecia?