Building fault-tolerant microservices with the @Fallback annotation

duration 20 minutes

Prerequisites:

You’ll explore how to manage the impact of failures using MicroProfile Fault Tolerance by adding fallback behavior to microservice dependencies.

What you’ll learn

You will learn how to use MicroProfile (MP) Fault Tolerance to build resilient microservices that reduce the impact from failure and ensure continued operation of services.

MP Fault Tolerance provides a simple and flexible solution to build fault-tolerant microservices. Fault tolerance leverages different strategies to guide the execution and result of logic. As stated in the MicroProfile website, retry policies, bulkheads, and circuit breakers are popular concepts in this area. They dictate whether and when executions take place, and fallbacks offer an alternative result when an execution does not complete successfully.

The application that you will be working with is an inventory service, which collects, stores, and returns the system properties. It uses the system service to retrieve the system properties for a particular host. You will add fault tolerance to the inventory service so that it reacts accordingly when the system service is unavailable.

You will use the @Fallback annotations from the MicroProfile Fault Tolerance specification to define criteria for when to provide an alternative solution for a failed execution.

You will also see the application metrics for the fault tolerance methods that are automatically enabled when you add the MicroProfile Metrics feature to your Open Liberty.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-microprofile-fallback.git
cd guide-microprofile-fallback

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first go to the finish directory and run the following Maven goal to build the application and deploy it to Open Liberty:

cd finish
mvn liberty:run

After you see the following message, your Liberty instance is ready:

The defaultServer server is ready to run a smarter planet.

Point your browser to the http://localhost:9080/inventory/systems/localhost URL, which accesses the inventory service with a localhost hostname. You see the system properties for this host. When you visit this URL, some of these system properties, such as the OS name and user name, are automatically stored in the inventory.

Update the CustomConfigSource configuration file.
resources/CustomConfigSource.json

CustomConfigSource.json

1{"config_ordinal":500,
2"io_openliberty_guides_system_inMaintenance":false}

Change the io_openliberty_guides_system_inMaintenance property from false to true and save the file.

You do not need to restart the Liberty instance. Next, return to your browser and point back to the http://localhost:9080/inventory/systems/localhost URL. The fallback mechanism is triggered because the system service is now in maintenance. You see the cached properties for this localhost.

When you are done checking out the application, go to the CustomConfigSource.json file again.

Update the CustomConfigSource configuration file.
resources/CustomConfigSource.json

Change the io_openliberty_guides_system_inMaintenance property from true to false to set this condition back to its original value.

After you are finished checking out the application, stop the Liberty instance by pressing CTRL+C in the command-line session where you ran Liberty. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:

mvn liberty:stop

Enabling fault tolerance

Navigate to the start directory to begin.

When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

The MicroProfile Fault Tolerance API is included in the MicroProfile dependency that is specified in your pom.xml file. Look for the dependency with the microprofile artifact ID. This dependency provides a library that allows you to use fault tolerance policies in your microservices.

You can also find the mpFaultTolerance feature in your src/main/liberty/config/server.xml configuration file, which turns on MicroProfile Fault Tolerance capabilities in Open Liberty.

To easily work through this guide, the two provided microservices are set up to run on the same Liberty instance. To simulate the availability of the services and then to enable fault tolerance, dynamic configuration with MicroProfile Configuration is used so that you can easily take one service or the other down for maintenance. If you want to learn more about setting up dynamic configuration, see Configuring microservices.

The following two steps set up the dynamic configuration on the system service and its client. You can move on to the next section, which adds the fallback mechanism on the inventory service.

First, the src/main/java/io/openliberty/guides/system/SystemResource.java file has the isInMaintenance() condition, which determines that the system properties are returned only if you set the io_openliberty_guides_system_inMaintenance configuration property to false in the CustomConfigSource file. Otherwise, the service returns a Status.SERVICE_UNAVAILABLE message, which makes it unavailable.

Next, the src/main/java/io/openliberty/guides/inventory/client/SystemClient.java file makes a request to the system service through the MicroProfile Rest Client API. If you want to learn more about MicroProfile Rest Client, you can follow the Consuming RESTful services with template interfaces guide. The system service as described in the SystemResource.java file may return a Status.SERVICE_UNAVAILABLE message, which is a 503 status code. This code indicates that the Liberty instance being called is unable to handle the request because of a temporary overload or scheduled maintenance, which would likely be alleviated after some delay. To simulate that the system is unavailable, an IOException is thrown.

The InventoryManager class calls the getProperties() method in the SystemClient.java class. You will look into the InventoryManager class in more detail in the next section.

SystemResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::503_response[]
13package io.openliberty.guides.system;
14
15import jakarta.enterprise.context.RequestScoped;
16import jakarta.ws.rs.GET;
17import jakarta.inject.Inject;
18import jakarta.ws.rs.core.Response;
19import jakarta.ws.rs.Path;
20import jakarta.ws.rs.Produces;
21import jakarta.ws.rs.core.MediaType;
22
23@RequestScoped
24@Path("properties")
25public class SystemResource {
26
27  @Inject
28  SystemConfig systemConfig;
29
30  @GET
31  @Produces(MediaType.APPLICATION_JSON)
32  public Response getProperties() {
33    // tag::isInMaintenance[]
34    if (!systemConfig.isInMaintenance()) {
35      return Response.ok(System.getProperties()).build();
36    // end::isInMaintenance[]
37    } else {
38    // tag::response-status[]
39      return Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
40    // end::response-status[]
41    }
42  }
43}
44// end::503_response[]

CustomConfigSource.json

1{"config_ordinal":500,
2"io_openliberty_guides_system_inMaintenance":false}

SystemClient.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::client[]
13package io.openliberty.guides.inventory.client;
14
15import java.util.Properties;
16import java.io.IOException;
17import jakarta.ws.rs.ProcessingException;
18import jakarta.ws.rs.GET;
19import jakarta.ws.rs.Path;
20import jakarta.ws.rs.Produces;
21import jakarta.ws.rs.core.MediaType;
22import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
23import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
24
25// tag::annotations[]
26
27@RegisterRestClient
28@RegisterProvider(ExceptionMapper.class)
29@Path("/properties")
30public interface SystemClient {
31  // end::annotations[]
32  @GET
33  @Produces(MediaType.APPLICATION_JSON)
34  // tag::getProperties[]
35  Properties getProperties()
36      // tag::IOException[]
37      throws UnknownUrlException, IOException, ProcessingException;
38      // end::IOException[]
39  // end::getProperties[]
40}
41// end::client[]

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>restfulWS-3.1</feature>
 5    <feature>jsonb-3.0</feature>
 6    <feature>jsonp-2.1</feature>
 7    <feature>cdi-4.0</feature>
 8    <feature>mpConfig-3.1</feature>
 9    <feature>mpRestClient-3.0</feature>
10    <!-- tag::mpFaultTolerance[] -->
11    <feature>mpFaultTolerance-4.0</feature>
12    <!-- end::mpFaultTolerance[] -->
13    <!-- tag::mpMetrics[] -->
14    <feature>mpMetrics-5.1</feature>
15    <!-- end::mpMetrics[] -->
16  </featureManager>
17
18  <variable name="http.port" defaultValue="9080"/>
19  <variable name="https.port" defaultValue="9443"/>
20
21  <!-- tag::quickStartSecurity[] -->
22  <quickStartSecurity userName="admin" userPassword="adminpwd"/>
23  <!-- end::quickStartSecurity[] -->
24
25  <httpEndpoint host="*" httpPort="${http.port}"
26    httpsPort="${https.port}" id="defaultHttpEndpoint"/>
27
28  <webApplication location="guide-microprofile-fallback.war" contextRoot="/"/>
29</server>

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<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">
  3
  4    <modelVersion>4.0.0</modelVersion>
  5
  6    <groupId>io.openliberty.guides</groupId>
  7    <artifactId>guide-microprofile-fallback</artifactId>
  8    <version>1.0-SNAPSHOT</version>
  9    <packaging>war</packaging>
 10
 11    <properties>
 12        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 13        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 14        <maven.compiler.source>11</maven.compiler.source>
 15        <maven.compiler.target>11</maven.compiler.target>
 16        <!-- Liberty configuration -->
 17        <liberty.var.http.port>9080</liberty.var.http.port>
 18        <liberty.var.https.port>9443</liberty.var.https.port>
 19    </properties>
 20
 21    <dependencies>
 22        <!-- Provided dependencies -->
 23        <dependency>
 24            <groupId>jakarta.platform</groupId>
 25            <artifactId>jakarta.jakartaee-api</artifactId>
 26            <version>10.0.0</version>
 27            <scope>provided</scope>
 28        </dependency>
 29        <!-- tag::microprofile[] -->
 30        <dependency>
 31            <groupId>org.eclipse.microprofile</groupId>
 32            <artifactId>microprofile</artifactId>
 33            <version>6.1</version>
 34            <type>pom</type>
 35            <scope>provided</scope>
 36        </dependency>
 37        <!-- end::microprofile[] -->
 38        <!-- For tests -->
 39        <dependency>
 40            <groupId>org.junit.jupiter</groupId>
 41            <artifactId>junit-jupiter</artifactId>
 42            <version>5.10.1</version>
 43            <scope>test</scope>
 44        </dependency>
 45        <dependency>
 46            <groupId>org.jboss.resteasy</groupId>
 47            <artifactId>resteasy-client</artifactId>
 48            <version>6.2.7.Final</version>
 49            <scope>test</scope>
 50        </dependency>
 51        <dependency>
 52            <groupId>org.jboss.resteasy</groupId>
 53            <artifactId>resteasy-json-binding-provider</artifactId>
 54            <version>6.2.7.Final</version>
 55            <scope>test</scope>
 56        </dependency>
 57        <dependency>
 58            <groupId>org.glassfish</groupId>
 59            <artifactId>jakarta.json</artifactId>
 60            <version>2.0.1</version>
 61            <scope>test</scope>
 62        </dependency>
 63        <!-- Java utility classes -->
 64        <dependency>
 65            <groupId>org.apache.commons</groupId>
 66            <artifactId>commons-lang3</artifactId>
 67            <version>3.14.0</version>
 68        </dependency>
 69    </dependencies>
 70
 71    <build>
 72        <finalName>${project.artifactId}</finalName>
 73        <plugins>
 74            <plugin>
 75                <groupId>org.apache.maven.plugins</groupId>
 76                <artifactId>maven-war-plugin</artifactId>
 77                <version>3.3.2</version>
 78            </plugin>
 79            <!-- Plugin to run unit tests -->
 80            <plugin>
 81                <groupId>org.apache.maven.plugins</groupId>
 82                <artifactId>maven-surefire-plugin</artifactId>
 83                <version>3.2.5</version>
 84            </plugin>
 85            <!-- Enable liberty-maven plugin -->
 86            <plugin>
 87                <groupId>io.openliberty.tools</groupId>
 88                <artifactId>liberty-maven-plugin</artifactId>
 89                <version>3.10</version>
 90            </plugin>
 91            <!-- Plugin to run functional tests -->
 92            <plugin>
 93                <groupId>org.apache.maven.plugins</groupId>
 94                <artifactId>maven-failsafe-plugin</artifactId>
 95                <version>3.2.5</version>
 96                <configuration>
 97                    <systemPropertyVariables>
 98                        <http.port>${liberty.var.http.port}</http.port>
 99                    </systemPropertyVariables>
100                </configuration>
101            </plugin>
102        </plugins>
103    </build>
104</project>

Adding the @Fallback annotation

The inventory service is now able to recognize that the system service was taken down for maintenance. An IOException is thrown to simulate the system service is unavailable. Now, set a fallback method to deal with this failure.

Replace the InventoryManager class.
src/main/java/io/openliberty/guides/inventory/InventoryManager.java

InventoryManager.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.inventory;
13
14import java.io.IOException;
15import java.net.UnknownHostException;
16import java.util.Properties;
17import java.util.ArrayList;
18import java.util.Collections;
19import java.util.List;
20import jakarta.enterprise.context.ApplicationScoped;
21import org.eclipse.microprofile.faulttolerance.Fallback;
22import io.openliberty.guides.inventory.model.InventoryList;
23import io.openliberty.guides.inventory.model.SystemData;
24
25@ApplicationScoped
26public class InventoryManager {
27
28    private List<SystemData> systems = Collections.synchronizedList(new ArrayList<>());
29    private InventoryUtils invUtils = new InventoryUtils();
30
31    // tag::Fallback[]
32    @Fallback(fallbackMethod = "fallbackForGet",
33            // tag::applyOn[]
34            applyOn = {IOException.class},
35            // end::applyOn[]
36            // tag::skipOn[]
37            skipOn = {UnknownHostException.class})
38            // end::skipOn[]
39    // end::Fallback[]
40    // tag::get[]
41    public Properties get(String hostname) throws IOException {
42        return invUtils.getProperties(hostname);
43    }
44    // end::get[]
45
46    // tag::fallbackForGet[]
47    public Properties fallbackForGet(String hostname) {
48        Properties properties = findHost(hostname);
49        if (properties == null) {
50            Properties msgProp = new Properties();
51            msgProp.setProperty(hostname,
52                    "System is not found in the inventory or system is in maintenance");
53            return msgProp;
54        }
55        return properties;
56    }
57    // end::fallbackForGet[]
58
59    public void add(String hostname, Properties systemProps) {
60        Properties props = new Properties();
61
62        String osName = systemProps.getProperty("os.name");
63        if (osName == null) {
64            return;
65        }
66
67        props.setProperty("os.name", systemProps.getProperty("os.name"));
68        props.setProperty("user.name", systemProps.getProperty("user.name"));
69
70        SystemData system = new SystemData(hostname, props);
71        if (!systems.contains(system)) {
72            systems.add(system);
73        }
74    }
75
76    public InventoryList list() {
77        return new InventoryList(systems);
78    }
79
80    private Properties findHost(String hostname) {
81        for (SystemData system : systems) {
82            if (system.getHostname().equals(hostname)) {
83                return system.getProperties();
84            }
85        }
86        return null;
87    }
88}

The @Fallback annotation dictates a method to call when the original method encounters a failed execution. In this example, use the fallbackForGet() method.

The @Fallback annotation provides two parameters, applyOn and skipOn, which allow you to configure which exceptions trigger a fallback and which exceptions do not, respectively. In this example, the get() method throws IOException when the system service is unavailable, and throws UnknownHostException when the system service cannot be found on the specified host. The fallbackForGet() method can handle the first case, but not the second.

The fallbackForGet() method, which is the designated fallback method for the original get() method, checks to see if the system’s properties exist in the inventory. If the system properties entry is not found in the inventory, the method prints out a warning message in the browser. Otherwise, this method returns the cached property values from the inventory.

You successfully set up your microservice to have fault tolerance capability.

Enabling metrics for the fault tolerance methods

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>restfulWS-3.1</feature>
 5    <feature>jsonb-3.0</feature>
 6    <feature>jsonp-2.1</feature>
 7    <feature>cdi-4.0</feature>
 8    <feature>mpConfig-3.1</feature>
 9    <feature>mpRestClient-3.0</feature>
10    <!-- tag::mpFaultTolerance[] -->
11    <feature>mpFaultTolerance-4.0</feature>
12    <!-- end::mpFaultTolerance[] -->
13    <!-- tag::mpMetrics[] -->
14    <feature>mpMetrics-5.1</feature>
15    <!-- end::mpMetrics[] -->
16  </featureManager>
17
18  <variable name="http.port" defaultValue="9080"/>
19  <variable name="https.port" defaultValue="9443"/>
20
21  <!-- tag::quickStartSecurity[] -->
22  <quickStartSecurity userName="admin" userPassword="adminpwd"/>
23  <!-- end::quickStartSecurity[] -->
24
25  <httpEndpoint host="*" httpPort="${http.port}"
26    httpsPort="${https.port}" id="defaultHttpEndpoint"/>
27
28  <webApplication location="guide-microprofile-fallback.war" contextRoot="/"/>
29</server>

MicroProfile Fault Tolerance integrates with MicroProfile Metrics to provide metrics for the annotated fault tolerance methods. When both the mpFaultTolerance and the mpMetrics features are included in the server.xml configuration file, the @Fallback fault tolerance annotation provides metrics that count the following things: the total number of annotated method invocations, the total number of failed annotated method invocations, and the total number of the fallback method calls.

The mpMetrics feature requires SSL and the configuration is provided for you. The quickStartSecurity configuration element provides basic security to secure the Liberty. When you go to the /metrics endpoint, use the credentials that are defined in the Liberty’s configuration to log in to view the data for the fault tolerance methods.

You can learn more about MicroProfile Metrics in the Providing metrics from a microservice guide. You can also learn more about the MicroProfile Fault Tolerance and MicroProfile Metrics integration in the MicroProfile Fault Tolerance specification.

Running the application

You started the Open Liberty in dev mode at the beginning of the guide, so all the changes were automatically picked up.

When the Liberty instance is running, point your browser to the http://localhost:9080/inventory/systems/localhost URL. You receive the system properties of your local JVM from the inventory service.

Next, point your browser to the system service URL, which is located at http://localhost:9080/system/properties, to retrieve the system properties for the specific localhost. Notice that the results from the two URLs are identical because the inventory service gets its results from calling the system service.

To see the application metrics, go to the https://localhost:9443/metrics?scope=base URL. Log in as the admin user, and use adminpwd as the password. See the following sample outputs for the @Fallback annotated method and the fallback method before a fallback occurs:

# TYPE base_ft_invocations_total counter
base_ft_invocations_total{fallback="notApplied",method="io.openliberty.guides.inventory.InventoryManager.get",result="valueReturned"} 1
base_ft_invocations_total{fallback="applied",method="io.openliberty.guides.inventory.InventoryManager.get",result="valueReturned"} 0
base_ft_invocations_total{fallback="notApplied",method="io.openliberty.guides.inventory.InventoryManager.get",result="exceptionThrown"} 0
base_ft_invocations_total{fallback="applied",method="io.openliberty.guides.inventory.InventoryManager.get",result="exceptionThrown"} 0

You can test the fault tolerance mechanism of your microservices by dynamically changing the io_openliberty_guides_system_inMaintenance property value to true in the resources/CustomConfigSource.json file, which puts the system service in maintenance.

Update the configuration file.
resources/CustomConfigSource.json

Change the io_openliberty_guides_system_inMaintenance property from false to true and save the file.

CustomConfigSource.json

1{"config_ordinal":500,
2"io_openliberty_guides_system_inMaintenance":false}

InventoryManager.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.inventory;
13
14import java.io.IOException;
15import java.net.UnknownHostException;
16import java.util.Properties;
17import java.util.ArrayList;
18import java.util.Collections;
19import java.util.List;
20import jakarta.enterprise.context.ApplicationScoped;
21import org.eclipse.microprofile.faulttolerance.Fallback;
22import io.openliberty.guides.inventory.model.InventoryList;
23import io.openliberty.guides.inventory.model.SystemData;
24
25@ApplicationScoped
26public class InventoryManager {
27
28    private List<SystemData> systems = Collections.synchronizedList(new ArrayList<>());
29    private InventoryUtils invUtils = new InventoryUtils();
30
31    // tag::Fallback[]
32    @Fallback(fallbackMethod = "fallbackForGet",
33            // tag::applyOn[]
34            applyOn = {IOException.class},
35            // end::applyOn[]
36            // tag::skipOn[]
37            skipOn = {UnknownHostException.class})
38            // end::skipOn[]
39    // end::Fallback[]
40    // tag::get[]
41    public Properties get(String hostname) throws IOException {
42        return invUtils.getProperties(hostname);
43    }
44    // end::get[]
45
46    // tag::fallbackForGet[]
47    public Properties fallbackForGet(String hostname) {
48        Properties properties = findHost(hostname);
49        if (properties == null) {
50            Properties msgProp = new Properties();
51            msgProp.setProperty(hostname,
52                    "System is not found in the inventory or system is in maintenance");
53            return msgProp;
54        }
55        return properties;
56    }
57    // end::fallbackForGet[]
58
59    public void add(String hostname, Properties systemProps) {
60        Properties props = new Properties();
61
62        String osName = systemProps.getProperty("os.name");
63        if (osName == null) {
64            return;
65        }
66
67        props.setProperty("os.name", systemProps.getProperty("os.name"));
68        props.setProperty("user.name", systemProps.getProperty("user.name"));
69
70        SystemData system = new SystemData(hostname, props);
71        if (!systems.contains(system)) {
72            systems.add(system);
73        }
74    }
75
76    public InventoryList list() {
77        return new InventoryList(systems);
78    }
79
80    private Properties findHost(String hostname) {
81        for (SystemData system : systems) {
82            if (system.getHostname().equals(hostname)) {
83                return system.getProperties();
84            }
85        }
86        return null;
87    }
88}

After saving the file, go back to your browser and refresh to the http://localhost:9080/inventory/systems/localhost URL to view the cached version of the properties. The fallbackForGet() method, which is the designated fallback method, is called when the system service is not available. The cached system properties contain only the OS name and user name key and value pairs.

To see that the system service is down, point your browser to the http://localhost:9080/system/properties URL again. You see that the service displays a 503 HTTP response code.

Go to the https://localhost:9443/metrics?scope=base URL again. See the following sample outputs for the @Fallback annotated method and the fallback method after a fallback occurs:

# TYPE base_ft_invocations_total counter
base_ft_invocations_total{fallback="notApplied",method="io.openliberty.guides.inventory.InventoryManager.get",result="valueReturned"} 1
base_ft_invocations_total{fallback="applied",method="io.openliberty.guides.inventory.InventoryManager.get",result="valueReturned"} 1
base_ft_invocations_total{fallback="notApplied",method="io.openliberty.guides.inventory.InventoryManager.get",result="exceptionThrown"} 0
base_ft_invocations_total{fallback="applied",method="io.openliberty.guides.inventory.InventoryManager.get",result="exceptionThrown"} 0

From the output, the base_ft_invocations_total{fallback="notApplied",method="io.openliberty.guides.inventory.InventoryManager.get",result="valueReturned"} data shows that the get() method was called once without triggering a fallback method. The base_ft_invocations_total{fallback="applied",method="io.openliberty.guides.inventory.InventoryManager.get",result="valueReturned"} data indicates that the get() method was called once and the fallback fallbackForGet() method was triggered.

Update the configuration file.
resources/CustomConfigSource.json

After you finish, change the io_openliberty_guides_system_inMaintenance property value back to false in the resources/CustomConfigSource.json file.

Testing the application

You can test your application manually, but automated tests ensure code quality because they trigger a failure whenever a code change introduces a defect. JUnit and the JAX-RS Client API provide a simple environment for you to write tests.

Create the FaultToleranceIT class.
src/test/java/it/io/openliberty/guides/faulttolerance/FaultToleranceIT.java

FaultToleranceIT.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2018, 2022 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12// tag::ft_testing[]
 13package it.io.openliberty.guides.faulttolerance;
 14
 15import static org.junit.jupiter.api.Assertions.assertEquals;
 16import static org.junit.jupiter.api.Assertions.assertTrue;
 17import jakarta.json.JsonObject;
 18import jakarta.ws.rs.client.Client;
 19import jakarta.ws.rs.client.ClientBuilder;
 20import jakarta.ws.rs.core.Response;
 21import org.junit.jupiter.api.AfterEach;
 22import org.junit.jupiter.api.BeforeEach;
 23import org.junit.jupiter.api.Test;
 24
 25import it.io.openliberty.guides.utils.TestUtils;
 26
 27public class FaultToleranceIT {
 28
 29    private Response response;
 30    private Client client;
 31
 32    // tag::Before[]
 33    @BeforeEach
 34    // end::Before[]
 35    public void setup() {
 36        client = ClientBuilder.newClient();
 37    }
 38
 39    // tag::After[]
 40    @AfterEach
 41    // end::After[]
 42    public void teardown() {
 43        client.close();
 44        response.close();
 45    }
 46    // tag::javadoc[]
 47    /**
 48     * testFallbackForGet - test for checking if the fallback is being called
 49     * correctly 1. Return system properties for a hostname when inventory
 50     * service is available. 2. Make System service down and get the system
 51     * properties from inventory service when it is down. 3. Check if system
 52     * properties for the specific host was returned when the inventory service
 53     * was down by: Asserting if the total number of the system properties, when
 54     * service is up, is greater than the total number of the system properties
 55     * when service is down.
 56     */
 57    // end::javadoc[]
 58
 59    // tag::Test1[]
 60    @Test
 61    // end::Test1[]
 62    // tag::testFallbackForGet[]
 63    public void testFallbackForGet() throws InterruptedException {
 64        response = TestUtils.getResponse(client,
 65                                         TestUtils.INVENTORY_LOCALHOST_URL);
 66        assertResponse(TestUtils.baseUrl, response);
 67        JsonObject obj = response.readEntity(JsonObject.class);
 68        int propertiesSize = obj.size();
 69        // tag::changeSystemProperty1[]
 70        TestUtils.changeSystemProperty(TestUtils.SYSTEM_MAINTENANCE_FALSE,
 71                                       TestUtils.SYSTEM_MAINTENANCE_TRUE);
 72        // end::changeSystemProperty1[]
 73        Thread.sleep(3000);
 74        response = TestUtils.getResponse(client,
 75                                         TestUtils.INVENTORY_LOCALHOST_URL);
 76        assertResponse(TestUtils.baseUrl, response);
 77        obj = response.readEntity(JsonObject.class);
 78        int propertiesSizeFallBack = obj.size();
 79        assertTrue(propertiesSize > propertiesSizeFallBack,
 80                   "The total number of properties from the @Fallback method "
 81                 + "is not smaller than the number from the system service"
 82                 +  "as expected.");
 83        // tag::changeSystemProperty2[]
 84        TestUtils.changeSystemProperty(TestUtils.SYSTEM_MAINTENANCE_TRUE,
 85                                       TestUtils.SYSTEM_MAINTENANCE_FALSE);
 86        // end::changeSystemProperty2[]
 87        Thread.sleep(3000);
 88    }
 89    // end::testFallbackForGet[]
 90
 91    // tag::javadoc[]
 92    /**
 93     * testFallbackForGet :
 94     * test for checking if the fallback skip mechanism is working as intended:
 95     * 1. Access system properties for the wrong hostname (localhot)
 96     * 2. Verify that the response code is 404
 97     * 3. Verify that the response text contains an error
 98     */
 99    // end::javadoc[]
100    // tag::Test2[]
101    @Test
102    // end::Test2[]
103    // tag::testFallbackSkipForGet[]
104    public void testFallbackSkipForGet() {
105        response = TestUtils.getResponse(client,
106                TestUtils.INVENTORY_UNKNOWN_HOST_URL);
107        assertResponse(TestUtils.baseUrl, response, 404);
108        assertTrue(response.readEntity(String.class).contains("error"),
109                   "Incorrect response body from "
110                   + TestUtils.INVENTORY_UNKNOWN_HOST_URL);
111    }
112    //end::testFallbackSkipForGet[]
113
114    // tag::javadoc[]
115    /**
116     * Asserts that the given URL's response code matches the given status code.
117     */
118    // end::javadoc[]
119    private void assertResponse(String url, Response response, int statusCode) {
120        assertEquals(statusCode, response.getStatus(),
121                "Incorrect response code from " + url);
122    }
123
124    // tag::javadoc[]
125    /**
126     * Asserts that the given URL has the correct response code of 200.
127     */
128    // end::javadoc[]
129    private void assertResponse(String url, Response response) {
130        assertResponse(url, response, 200);
131    }
132}
133// end::ft_testing[]

The @BeforeEach and @AfterEach annotations indicate that this method runs either before or after the other test case. These methods are generally used to perform any setup and teardown tasks. In this case, the setup method creates a JAX-RS client, which makes HTTP requests to the inventory service. This client must also be registered with a JSON-P provider to process JSON resources. The teardown method simply destroys this client instance as well as the HTTP responses.

The testFallbackForGet() test case sends a request to the inventory service to get the systems properties for a hostname before and after the system service becomes unavailable. Then, it asserts outputs from the two requests to ensure that they are different from each other.

The testFallbackSkipForGet() test case sends a request to the inventory service to get the system properties for an incorrect hostname (unknown). Then, it confirms that the fallback method has not been called by asserting that the response’s status code is 404 with an error message in the response body.

The @Test annotations indicate that the methods automatically execute when your test class runs.

In addition, a few endpoint tests have been included for you to test the basic functionality of the inventory and system services. If a test failure occurs, then you might have introduced a bug into the code.

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

If the tests pass, you see a similar output to the following example:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.faulttolerance.FaultToleranceIT
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.517 sec - in it.io.openliberty.guides.faulttolerance.FaultToleranceIT
Running it.io.openliberty.guides.system.SystemEndpointIT
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.937 sec - in it.io.openliberty.guides.system.SystemEndpointIT
Running it.io.openliberty.guides.inventory.InventoryEndpointIT
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.396 sec - in it.io.openliberty.guides.inventory.InventoryEndpointIT

Results :

Tests run: 6, Failures: 0, Errors: 0, Skipped: 0

To see if the tests detect a failure, comment out the changeSystemProperty() methods in the FaultToleranceIT.java file. Rerun the tests to see that a test failure occurs for the testFallbackForGet() and testFallbackSkipForGet() test cases.

When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran Liberty.

Great work! You’re done!

You just learned how to build a fallback mechanism for a microservice with MicroProfile Fault Tolerance in Open Liberty and wrote a test to validate it.

You can try one of the related MicroProfile guides. They demonstrate technologies that you can learn and expand on what you built here.

Guide Attribution

Building fault-tolerant microservices with the @Fallback annotation by Open Liberty is licensed under CC BY-ND 4.0

Copy file contents
Copied to clipboard

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star