Desenvolvimento

25 jul, 2017

Hystrix circuit breaker

Publicidade

Olá, no artigo anterior, nós consumimos um serviço utilizando o Feig para fazer uma busca por um determinado CEP, mas nem tudo são flores e esse serviço pode estar fora do ar e a requisição não ser completada. Aí, teremos um problema, mas quase tudo tem solução e para esse problemas temos o Hystrix.

O Hystrix implementa o padrão Circuit Breaker, que de forma bem rápida é um failover para chamadas entre micro serviços, ou seja, caso um micro serviço estiver fora do ar um método de fallback é chamado e aquela enxurrada de falhas é evitada.

Vamos usar o exemplo do artigo anterior (baixe o código aqui). Bom, o que vai ser feito é o seguinte, todas as requisições realizadas com sucesso vão ter os seus dados salvos em um MongoDB (poderia ser um Redis, ou deveria ser, mas o Mongo já estava aqui na máquina rs) e caso não seja fechada a conexão com o Postmon, será feita uma busca no MongoDB, se mesmo assim não for encontrada nenhuma informação para aquele CEP ai retornará um erro, afinal não se pode fazer milagre.

Agora é hora de colocar a mão no código. Primeiramente, é preciso adicionar as libs do Hystrix e do Spring Data MongoDB no projeto, basta adicionar isso no pom.xml.

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-hystrix</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-mongodb</artifactId>
 </dependency>

Dependências adicionadas, é hora de colocar as anotações do mongo na classe de response para que ela possa ser salva, veja como ela tem que ficar:

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
 
@Data
@Document
public class CepResponse {
 
 @Id
 private String cep;
 private String logradouro;
 private String bairro;
 private String cidade;
 private String estado;
}

Agora, essa classe já pode ser salva no Mongo, o atributo “cep” é o Id e fica fácil fazer busca e o campo é automaticamente indexado.

Agora é hora de criar o repositório para esse documento do Mongo, para isso é só criar uma interface igual a exibida abaixo:

import br.com.easy.cep.integration.CepResponse;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
 
@Repository
public interface CepRepository extends PagingAndSortingRepository<CepResponse, String> {}
Após a interface criada é preciso salvar os dados quando a chamada for efetuada com sucesso, para isso é só alterar o serviço rest e deixá-lo assim:
 
import br.com.easy.cep.integration.CepResponse;
import br.com.easy.cep.integration.CepService;
import br.com.easy.cep.repository.CepRepository;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
 
@RestController
@AllArgsConstructor
@RequestMapping("v1/ceps")
public class CepRestService {
 
    private final CepService cepService;
    private final CepRepository cepRepository;
 
    @ResponseStatus(HttpStatus.OK)
    @RequestMapping(value = "/{cep}", method = RequestMethod.GET)
    public CepResponse getCep(@PathVariable String cep) {
        return cepRepository.save(cepService.getCep(cep));
    }
}

Feito isso, os dados retornados vão ser salvos no Mongo e podem ser utilizados caso algum problema aconteça com a chamada da API (Postmon), mas ainda assim pode ser que não tenhamos a informação buscada então vou criar uma exception “bonita” para que ela seja exibida no momento da falha da falha rs.

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
 
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {}

Por último, é só criar a classe que será chamada no caso de erro na integração e avisar o Feign que um fallback foi configurado, então, para isso é só criar uma nova classe que implementa a interface do Feig:

import br.com.easy.cep.exception.ResourceNotFoundException;
import br.com.easy.cep.repository.CepRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
 
import static java.util.Objects.isNull;
 
@Component
@AllArgsConstructor
public class CepServiceFallback implements CepService {
 
    private final CepRepository cepRepository;
 
    @Override
    public CepResponse getCep(String cep) {
        CepResponse cepResponse = cepRepository.findOne(cep);
        if(isNull(cepResponse)) {
            throw new ResourceNotFoundException();
        }
        return cepResponse;
    }
}

O que essa classe faz é buscar no Mongo e caso não encontre ela devolve uma exception. Agora, para avisar o Feign do fallback, basta adicionar o parâmetro fallback na anotação FeigClient:

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
 
@FeignClient(name = "cepService", url = "http://api.postmon.com.br", fallback = CepServiceFallback.class)
public interface CepService {
 
    @RequestMapping("/v1/cep/{cep}")
    CepResponse getCep(@PathVariable("cep") String cep);
}

A configuração do tempo de timeout é definida no application.propertie ou application.yaml como exemplificado abaixo:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 200

Para que sua aplicação habilite o Hystrix e seu Dashboard é preciso adicionar duas anotações na sua classe de startup:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
 
@EnableHystrix
@EnableFeignClients
@SpringBootApplication
@EnableHystrixDashboard
public class EasyCepApplication {
 
   public static void main(String[] args) {
      SpringApplication.run(EasyCepApplication.class, args);
   }
}

E é isso! Agora, a aplicação tem uma tolerância maior a falha e você pode dormir um pouco mais tranquilo, espero que tenham gostado do artigo e qualquer dúvida é só comentar.

Até o próximo!