= MicroProfile Fault Tolerance - Retry Policy
:index-group: MicroProfile
:jbake-type: page
:jbake-status: published

Este es un ejemplo de cómo usar Microprofile @Retry en TomEE.

== Retry Feature

Microprofile Fault Tolerance tiene una función llamada Reintentar que se puede utilizar
para recuperase de una operación del error, invocando la misma operación de nuevo
hasta que se alcancen los criterios de detención.

La directiva de reintento permite configurar:

*  *maxRetries*: los reintentos máximos
*  *delay*: retrasos entre cada reintento
*  *delayUnit*: la unidad de retardo
*  *maxDuration*: duración máxima para realizar el reintento.
*  *durationUnit*: unidad de duración
*  *jitter:* la variación aleatoria de retrasos de reintento
*  *jitterDelayUnit:* la unidad de fluctuación
*  *retryOn:* especifica reintentar en caso de errores
*  *abortOn:* especifica anular en caso de errores

Para utilizar esta función puede anotar una clase y/o método con la anotación `@Retry`. Compruebe la
http://download.eclipse.org/microprofile/microprofile-fault-tolerance-1.1/microprofile-fault-tolerance-spec.html[especificación]
para más detalles.

== Ejemplos

=== Ejecutar la aplicación

....
mvn clean install tomee:run
....

=== Ejemplo 1

El método statusOfDay producirá un error tres veces, cada vez, 
lanzando una 'WeatherGatewayTimeoutException' y como la anotación @Retry 
está configurada para `retryOn` en caso de error, la libreria FailSafe tomará 
el valor `maxRetry` y volverá a intentar la misma operación hasta que alcance 
el número máximo de intentos, que es 3 (valor predeterminado).

[source,java]
----
@RequestScoped
public class WeatherGateway{ 
   ...
   @Retry(maxRetry=3, retryOn = WeatherGatewayTimeoutException.class)
   public String statusOfDay(){
       if(counterStatusOfDay.addAndGet(1) <= DEFAULT_MAX_RETRY){
           LOGGER.warning(String.format(FORECAST_TIMEOUT_MESSAGE, DEFAULT_MAX_RETRY, counterStatusOfDay.get()));
           throw new WeatherGatewayTimeoutException();
       }
       return "Today is a sunny day!";
   }
   ...
 }
----

Llamada de estado del día

....
GET http://localhost:8080/mp-faulttolerance-retry/weather/day/status
....

Registro del servidor

....
WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (1)
WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (2)
WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (3)
....

Respuesta

....
Today is a sunny day! (¡Hoy es un día soleado!)
....

=== Ejemplo 2

El método weekStatus fallará dos veces, cada vez, lanzando una excepción
`WeatherGatewayTimeoutException` porque `retryOn` está configurado y
en lugar de devolver una respuesta al autor de la llamada, la lógica indica que en
el tercer intento, una excepción `WeatherGatewayBusyServiceException` será
lanzada. Como la anotación `@Retry` está configurada para `abortOn` en caso que
`WeatherGatewayTimeoutException` ocurra, el intento restante no será
ejecutado y el autor de la llamada tendra que controlar la excepción.

[source,java]
----
@Retry(maxRetries = 3, retryOn = WeatherGatewayTimeoutException.class, abortOn = WeatherGatewayBusyServiceException.class)
public String statusOfWeek(){
    if(counterStatusOfWeek.addAndGet(1) <= DEFAULT_MAX_RETRY){
        LOGGER.warning(String.format(FORECAST_TIMEOUT_MESSAGE_ATTEMPTS, DEFAULT_MAX_RETRY, counterStatusOfWeek.get()));
        throw new WeatherGatewayTimeoutException();
    }
    LOGGER.log(Level.SEVERE, String.format(FORECAST_BUSY_MESSAGE, counterStatusOfWeek.get()));
    throw new WeatherGatewayBusyServiceException();
}
----

Llamada de estado de la semana

....
GET http://localhost:8080/mp-faulttolerance-retry/weather/week/status
....

Registro del servidor

....
WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (1)
WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (2)
WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (3)
SEVERE  - Error AccuWeather Forecast Service is busy. Number of Attempts: (4) 
....

Respuesta

....
WeatherGateway Service is Busy. Retry later
....

=== Ejemplo 3

La anotación `@Retry` permite configurar un retraso para ejecutar cada nuevo intento
dando la oportunidad al servicio solicitado para recuperarse y contestador de la 
solicitud correctamente. Para cada nuevo reintento siga el retraso
configurar, es necesario establecer `jitter` a cero (0). De lo contrario, el retraso de
cada nuevo intento será aleatorio.

Analizando los mensajes registrados, es posible ver que todos los intentos
toman más o menos el mismo tiempo para ejecutar.

[source,java]
----
@Retry(retryOn = WeatherGatewayTimeoutException.class, maxRetries = 5, delay = 500, jitter = 0)
public String statusOfWeekend() {
    if (counterStatusOfWeekend.addAndGet(1) <= 5) {
        logTimeoutMessage(statusOfWeekendInstant);
        statusOfWeekendInstant = Instant.now();
        throw new WeatherGatewayTimeoutException();
    }
    return "The Forecast for the Weekend is Scattered Showers.";
}
----

Llamada de estado de la semana

....
GET http://localhost:8080/mp-faulttolerance-retry/weather/weekend/status
....

Registro del servidor

....
WARNING - Timeout when accessing AccuWeather Forecast Service.
WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (501) millis
WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (501) millis
WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (501) millis
WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (500) millis
....

=== Ejemplo 4 

Básicamente con el mismo comportamiento del `Ejemplo 3`, este ejemplo establece 
el `delay` y el `jitter` con 500 milisegundos para crear aleatoriamente un nuevo retardo 
para cada nuevo intento después del primer error. 
https://github.com/jhalterman/failsafe/blob/master/src/main/java/net/jodah/failsafe/AbstractExecution.java[AbstractExecution-randomDelay(delay,jitter,random)] puede dar una idea de cómo se calcula el nuevo retraso.  

Mediante el análisis de los mensajes registrados, es posible ver cuanto
cada intento tiene que esperar antes de ejecutarse. 

[source,java]
----
@Retry(retryOn = WeatherGatewayTimeoutException.class, delay = 500, jitter = 500)
public String statusOfMonth() {
    if (counterStatusOfWeekend.addAndGet(1) <= DEFAULT_MAX_RETRY) {
        logTimeoutMessage(statusOfMonthInstant);
        statusOfMonthInstant = Instant.now();
        throw new WeatherGatewayTimeoutException();
    }
    return "The Forecast for the Weekend is Scattered Showers.";
}
----

Llamada de estado del mes

....
GET http://localhost:8080/mp-faulttolerance-retry/weather/month/status
....

Registro del servidor

....
WARNING - Timeout when accessing AccuWeather Forecast Service.
WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (417) millis
WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (90) millis
....

=== Ejemplo 5 

Si no se establece una condición para que una operación que se vuelva a ejecutar 
como en los ejemplos anteriores mediante el parámetro `retryOn`, la operación 
se ejecuta de nuevo para _cualquier_ excepción que se produce.

[source,java]
----
@Retry(maxDuration = 1000)
public String statusOfYear(){
    if (counterStatusOfWeekend.addAndGet(1) <= 5) {
        logTimeoutMessage(statusOfYearInstant);
        statusOfYearInstant = Instant.now();
        throw new RuntimeException();
    }
    return "WeatherGateway Service Error";
}
----

Llamada de estado del año

....
GET http://localhost:8080/mp-faulttolerance-retry/weather/year/statusk
....

Registro del servidor

....
WARNING - Timeout when accessing AccuWeather Forecast Service.
WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (666) millis
WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (266) millis
WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (66) millis
....

=== Ejecutar las pruebas

También puede probarlo utilizando el enlace:src/test/java/org/superbiz/rest/WeatherServiceTest.java[WeatherServiceTest.java] disponible en el proyecto.

....
mvn clean test
....

....
[INFO] Results:
[INFO] 
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
....
