Usando o fault tolerance no Quarkus, porque errar é umano, mas tem limites!
Tags: quarkus, java, braziliandevs
Published: 2021-05-18 23:46:58

Encare os fatos, em algum momento a sua aplicação vai falhar. 

Porém, não é porque erramos que vamos precisamos chorando em posição fetal por termos falhando. Nós podemos nos recuperar facilmente e o Quarkus nos auxilia nesse processo. 

Então, se você souber como errar ou como se recuperar desse erro, a sua aplicação ficará bem. O Quarkus traz uma extensão batuta para lidar com essas situações: a *fault tolerance*. No post de hoje, vamos conhecer essa extensão.

#O que faremos
Vamos construir um microsserviço com alguns endpoints REST. Esses endpoints vão falhar de diferentes formas. Após isso, vamos implementar soluções simples e elegantes para contornar essas falhas.

#É hora da ação
Vamos começar indo no [code.quarkus.io](https://code.quarkus.io/), selecionar a extensão **SmallRye Fault Tolerance**, mandar baixar com código de exemplo. Isso criará um serviço rest bem simples que retornará uma String de **hello**. Vamos mudar um pouco a classe criada e criando alguns endpoints que simularão diferentes situações de erro e que nós iremos tratar. O código com essas situações ficará assim:

```java
package org.acme;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import java.time.LocalDateTime;

@Path("/")
public class ExampleResource {
  int contador = 0;

  @GET
  @Path("/timeout")
  public String helloComTimeout() throws Exception {
      return tentativaComSleep();
  }

  String tentativaComSleep() throws Exception {
    Thread.sleep(5_000);
    return "hello com timeout";
  }

  @GET
  @Path("/retries")
  public String helloComRetry() throws Exception {
    tentativaComRetentativas();
    return "hello com retry";
  }

  private void tentativaComRetentativas() {
    contador++;
    System.out.println("Tentativa número: " + contador + " às " + LocalDateTime.now());
    throw new RuntimeException();
  }

  @GET
  @Path("/fallback")
  public String helloComFallback() throws Exception {
    return tentativaComFallback();
  }

  String tentativaComFallback() {
    throw new RuntimeException();
  }  
}
```

Criamos 3 endpoints. O primeiro responde, porém demora algum tempo para isso, o segundo e o terceiro não conseguem responder por causa de alguma exceção. Agora nós vamos contornar essas situações com o suporte da nossa biblioteca de tolerância a falhas.

##Timeout
A condição de timeout é ativada quando o sistema demora tempo demais para responder. 
O contorno dela se baseia em criar uma *thread* para ficar monitorando o tempo de início de execução e estourar uma exception caso esse tempo seja excedido. 

Porém, não precisamos escrever muito para ter tudo isso.
Basta adicionar a anotação **@Timeout** com o tempo limite que nós queremos. Caso o método não complete a operação a tempo, será lançada uma exceção do tipo **TimeoutException.** 

Depois disso é só será tratar essa exceção com o comportamento que desejado. Dessa forma, o Quarkus controla toda essa parte de controle de tempo, *threads*, etc...

Alterando o código da requisição, ficará assim:

```java

  @GET
  @Path("/timeout")
  public String helloComTimeout() throws Exception {
    try {
      return tentativaComSleep();
    } catch (TimeoutException e) {
      return "Tentativa com timeout tratado, seu serviço responderá rápido";
    }
  }

  @Timeout(value = 2000)
  private String tentativaComSleep() throws Exception {
    Thread.sleep(5_000);
    return "hello com timeout";
  }
```

Nosso código passará a responder mais rápido (ainda que seja com um comportamento diferente), fazendo com que a experiência do usuário seja mais suave.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9lqmxh2v9s4ns38529f0.png)

##Retentativas

Às vezes, o problema pode não ser que o serviço demore demais para responder, talvez o problema pode ser resolvido se nós tentarmos novamente algum tempo depois.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w2wv29k7b06ysb492xqj.jpg)

Com a anotação @Retry, caso ocorra alguma exceção, a aplicação tentará executar o mesmo código várias vezes (até um limite estabelecido). Caso não consiga executar, daí então é que ela vai realmente estourar a exceção. O código com a retentativa fica assim:

```java

  @Retry(maxRetries = 5)
  void tentativaComException() {
    contador++;
    System.out.println("Tentativa número: " + contador + " às " + LocalDateTime.now());
    throw new RuntimeException();
  }
```

O nosso código ainda retornará exceção, porém, se formos no log veremos as vezes que o sistema tentou executar o método.

```
Tentativa número: 1 às 2021-05-18T10:24:37.996164
Tentativa número: 2 às 2021-05-18T10:24:38.000239
Tentativa número: 3 às 2021-05-18T10:24:38.101461
Tentativa número: 4 às 2021-05-18T10:24:38.292833
Tentativa número: 5 às 2021-05-18T10:24:38.338367
Tentativa número: 6 às 2021-05-18T10:24:38.338657
```

Notem que só por ter essa anotação, nós ganhamos o comportamento das retentativas e também temos vários tipos de controle como o tempo entre retentativas, o tempo máximo para fazer toda a operação, para quais exceções terá rentativa, etc...

##Fallback
O método de fallback é o método que é chamado no caso de uma exceção. Se na retentativa o sistema executava várias vezes o mesmo método, com o fallback será executado alguma outra lógica. Podemos pensar nele como uma forma mais limpa de fazer um **try/catch**. O código com alterado fica assim:

```java
  @Fallback(fallbackMethod = "tratamentoComFallback")
  String tentativaComFallback() {
    throw new RuntimeException();
  }

  String tratamentoComFallback() {
    return "Hello com fallback tratado";
  }
```
Executando o código, o retorno será:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gpd83d6rx434s92xxpgv.png)

É importante que o método de fallback tenha a mesma assinatura do método inicial ou dará ruim. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q3tumysnudjcgnb96zu6.jpg)

Outra coisa bem parte legal do método de fallback é que ele trabalha muito bem com as outras anotações. Dessa forma você pode retentar fazer uma operação e, caso não consiga ou demore muito tempo, o método de fallback é executado. Fica topzeira.

Por fim, existe um ponto de enorme atenção a todos. Os métodos com essas anotações não serão processados se o escopo do método for privado. Então muita atenção nesse ponto.

#Considerações finais
Fiquei bem feliz em conseguir voltar a escrever sobre Quarkus, principalmente por falar sobre essa extensão que tem sido uma mão na roda nas últimas semanas. Num post futuro, quero falar sobre [*circuit breaker*](https://www.treinaweb.com.br/blog/o-que-e-circuit-breaker/).

Espero que cada vez mais as pessoas possam usar e se apaixonar por essa tecnologia incrível.
Ah, e o código de hoje pode ser encontrado no [github](https://github.com/lucasscharf/blog-posts-code/tree/master/fault-tolerance).