Quarkus provide a convenient library when connecting to an unreliable third party external system, and that is SmallRye Fault Tolerance. In this sample, we are trying to simulate a connection to an external website, which is reqres.in, while creating a random IOException.
Based on above scenario, we will try to retry the connection when we arent able to connect to our backend services, but retries will happen at most 4times and only on specific defined exceptions.
So lets start with a regular maven file,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.edw</groupId>
<artifactId>quarkus-retry</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skipITs>true</skipITs>
<surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
<compiler-plugin.version>3.10.1</compiler-plugin.version>
<!-- quarkus -->
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>2.16.6.Final</quarkus.platform.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-fault-tolerance</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
</project>
And create application.properties to put all apps configuration there
# default quarkus.http.port=8080 quarkus.log.level=INFO quarkus.log.category."com.edw".level=DEBUG # rest client com.edw.client.UserRestClient/mp-rest/url=https://reqres.in/
Next is to create a RestClient that will do a rest api call to an external 3rd party
@ApplicationScoped
@RegisterRestClient
@Path("/api")
public interface UserRestClient {
@GET
@Path("/users/{id}")
Users get(@PathParam("id") Integer id);
}
And the create a Service class that will call the RestClient. In this class we are simulating an Exception randomly.
@ApplicationScoped
public class UserService {
private Logger logger = LoggerFactory.getLogger(this.getClass().getName());
@Inject
@RestClient
UserRestClient userRestClient;
public Users getUser(Integer id) throws IOException {
Random random = new Random();
if(random.nextBoolean()) {
logger.debug("==== simulate random exception ====");
throw new IOException();
}
return userRestClient.get(id);
}
}
And last, is our Controller class,
@Path("/")
public class HelloWorldController {
private Logger logger = LoggerFactory.getLogger(this.getClass().getName());
@Inject
UserService userService;
@GET
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response index() {
return Response
.status(200)
.entity(new Hello("world"))
.build();
}
@GET
@Path("/user/{id}")
@Produces(MediaType.APPLICATION_JSON)
@Retry(maxRetries = 1, retryOn= IOException.class)
@Fallback(fallbackMethod = "getEmptyUser")
public Response getUser(@PathParam("id") Integer id) throws IOException {
Users users = userService.getUser(id);
return Response
.status(200)
.entity(users)
.build();
}
public Response getEmptyUser(Integer id) throws IOException {
logger.debug("==== giving default response ====");
return Response
.status(200)
.entity(new Users())
.build();
}
}
We can run our Quarkus project by using below command,
$ mvn quarkus:dev
And do some rest api call to it, a successful response would looks like this
$ curl -kv http://localhost:8080/user/2
* Trying ::1:8080...
* TCP_NODELAY set
* Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /user/2 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.65.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json
< content-length: 280
<
* Connection #0 to host localhost left intact
{"data":{"id":2,"email":"janet.weaver@reqres.in","first_name":"Janet","last_name":"Weaver","avatar":"https://reqres.in/img/faces/2-image.jpg"},"support":{"url":"https://reqres.in/#support-heading","text":"To keep ReqRes free, contributions towards server costs are appreciated!"}}
while a failed one will give below response,
$ curl -kv http://localhost:8080/user/2
* Trying ::1:8080...
* TCP_NODELAY set
* Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /user/2 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.65.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json
< content-length: 28
<
* Connection #0 to host localhost left intact
{"data":null,"support":null}
Code for this post can be found on below link,
https://github.com/edwin/quarkus-smallrye-retry