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, 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:
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:
@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.
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.
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:
@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:
@Fallback(fallbackMethod = "tratamentoComFallback")
String tentativaComFallback() {
throw new RuntimeException();
}
String tratamentoComFallback() {
return "Hello com fallback tratado";
}
Executando o código, o retorno será:
É importante que o método de fallback tenha a mesma assinatura do método inicial ou dará ruim.
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.
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.
Top comments (2)
Parabéns pelo excelente post.
Muito bom o artigo João!