Adding health reports to microservices

duration 20 minutes

Explore how to report and check the health of a microservice with MicroProfile Health.

What you’ll learn

You will learn how to use MicroProfile Health to report the health status of microservices and take appropriate actions based on this report.

MicroProfile Health allows services to report their health, and it publishes the overall health status to a defined endpoint. A service reports UP if it is available and reports DOWN if it is unavailable. MicroProfile Health reports an individual service status at the endpoint and indicates the overall status as UP if all the services are UP. A service orchestrator can then use the health statuses to make decisions.

A service checks its own health by performing necessary self-checks and then reports its overall status by implementing the API provided by MicroProfile Health. A self-check can be a check on anything that the service needs, such as a dependency, a successful connection to an endpoint, a system property, a database connection, or the availability of required resources.

You will add self-checks to the system and inventory services, which have been provided for you, and implement what is necessary to report health status by using MicroProfile Health.

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-health.git
cd guide-microprofile-health

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

The finish directory contains the finished project, which is what you will build.

Try what you’ll build

The finish directory in the root directory of this guide contains two services that are configured to use MicroProfile Health. Feel free to give them a try before you proceed.

To try out the services, navigate to the finish directory and then run the Maven install and liberty:start-server goals to build the services and run them in Open Liberty:

cd finish
mvn install liberty:start-server

The system and inventory services can be found at the following URLs:

Visit the http://localhost:9080/health URL to see the health report of the two services as well as the overall health status of the application. One check shows the state of the system service, and the other shows the state of the inventory service. As you might expect, both services are in the UP state, and the overall health status of the application is UP.

When you are done checking out the services, stop the Open Liberty server by running the following command:

mvn liberty:stop-server

Adding health checks to microservices

Navigate to the start directory to begin.

A health report will be generated automatically for all services that enable MicroProfile Health. This feature has already been enabled for you in the src/main/liberty/config/server.xml file:

<feature>mpHealth-1.0</feature>

All services must provide an implementation of the HealthCheck interface, which will be used to verify their health.

Adding a health check to the system service

Implement the HealthCheck interface in the src/main/java/io/openliberty/guides/system/SystemHealth.java file:

package io.openliberty.guides.system;

import javax.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.health.Health;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;

@Health
@ApplicationScoped
public class SystemHealth implements HealthCheck {
  @Override
  public HealthCheckResponse call() {
    if (!System.getProperty("wlp.server.name").equals("defaultServer")) {
      return HealthCheckResponse.named(SystemResource.class.getSimpleName())
                                .withData("default server", "not available").down()
                                .build();
    }
    return HealthCheckResponse.named(SystemResource.class.getSimpleName())
                              .withData("default server", "available").up().build();
  }
}

The @Health annotation indicates that this particular bean implements the HealthCheck interface. By pairing this annotation with the ApplicationScoped context from the Contexts and Dependency Injections API, the bean is discovered automatically when the http://localhost:9080/health endpoint receives a request.

The call() method is used to return the health status of a particular service. In this case, you are simply checking if the server name is defaultServer and returning UP if it is, and DOWN otherwise. The HealthCheckResponse.named() method is used to indicate what service the health check is done for. Overall, this is a very simple implementation of the call() method. In a real development environment, you would want to orchestrate much more meaningful health checks.

Adding a health check to the inventory service

Implement the HealthCheck interface in the src/main/java/io/openliberty/guides/inventory/InventoryHealth.java file:

package io.openliberty.guides.inventory;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import io.openliberty.guides.inventory.client.SystemClient;
import org.eclipse.microprofile.health.Health;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;

@Health
@ApplicationScoped
public class InventoryHealth implements HealthCheck {
  @Inject
  InventoryConfig config;

  private InventoryUtils invUtils = new InventoryUtils();

  public boolean isHealthy() {
    if (config.isInMaintenance()) {
      return false;
    }
    try {
      String url = invUtils.buildUrl("http", "localhost",
          Integer.parseInt(System.getProperty("default.http.port")),
          "/system/properties");
      Client client = ClientBuilder.newClient();
      Response response = client.target(url).request(MediaType.APPLICATION_JSON)
                                .get();
      if (response.getStatus() != 200) {
        return false;
      }
      return true;
    } catch (Exception e) {
      return false;
    }
  }

  @Override
  public HealthCheckResponse call() {
    if (!isHealthy()) {
      return HealthCheckResponse.named(InventoryResource.class.getSimpleName())
                                .withData("services", "not available").down()
                                .build();
    }
    return HealthCheckResponse.named(InventoryResource.class.getSimpleName())
                              .withData("services", "available").up().build();
  }

}

This time, you are checking whether or not the service is in maintenance or if it’s down. For simplicity, a custom io_openliberty_guides_inventory_inMaintenance MicroProfile Config property defined in the resources/CustomConfigSource.json file is used to indicate whether the service is in maintenance or not. This file has already been created for you. To check if the service is down, simply make a HTTP GET request to the system service and check the status returned by the response. You make a GET request to the system service rather than the inventory service because the inventory service depends on the system service. In other words, the inventory service wouldn’t work if the system service is down. If the status is not 200, then the service is not running. Based on these two factors, the isHealthy() method returns whether or not the inventory service is healthy.

If you are curious about the injected inventoryConfig object or if you want more information on MicroProfile Config, see Configuring microservices.

Building and running the application

To build the application, run the Maven install phase from the command line in the start directory:

mvn install

This command builds the application and creates a .war file in the target directory. It also configures and installs Open Liberty into the target/liberty/wlp directory.

Next, run the Maven liberty:start-server goal:

mvn liberty:start-server

This goal starts an Open Liberty server instance. Your Maven pom.xml is already configured to start the application in this server instance.

While the server is running, navigate to the http://localhost:9080/health URL to find the the health report on the two services.

Put the inventory service in maintenance by setting the io_openliberty_guides_inventory_inMaintenance property to true in the resources/CustomConfigSource.json file. Because this configuration file is picked up dynamically, simply refresh the http://localhost:9080/health URL you will see that the state of the inventory service has changed to DOWN. The overall state of the application has also changed to DOWN as a result. Point to the http://localhost:9080/inventory/systems URL to verify that the inventory service is indeed in maintenance. Set the io_openliberty_guides_inventory_inMaintenance property back to false once you are done.

Testing health checks

You will implement two test methods, testIfServicesAreUp() and testIfInventoryServiceIsDown(), to validate the health of the system and inventory services.

Begin by creating a test class in the src/test/java/it/io/openliberty/guides/health/HealthTest.java file:

package it.io.openliberty.guides.health;

import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import javax.json.JsonArray;
import org.junit.After;
import org.junit.Test;

public class HealthTest {

    private JsonArray servicesStates;
    private static HashMap<String, String> dataWhenServicesUP;
    private static HashMap<String, String> dataWhenInventoryDown;

    static {
        dataWhenServicesUP = new HashMap<String, String>();
        dataWhenInventoryDown = new HashMap<String, String>();

        dataWhenServicesUP.put("SystemResource", "UP");
        dataWhenServicesUP.put("InventoryResource", "UP");

        dataWhenInventoryDown.put("SystemResource", "UP");
        dataWhenInventoryDown.put("InventoryResource", "DOWN");
    }

    @Test
    public void testIfServicesAreUp() {
        servicesStates = HealthTestUtil.connectToHealthEnpoint(200);
        checkStates(dataWhenServicesUP, servicesStates);
    }

    @Test
    public void testIfInventoryServiceIsDown() {
        servicesStates = HealthTestUtil.connectToHealthEnpoint(200);
        checkStates(dataWhenServicesUP, servicesStates);
        HealthTestUtil.changeInventoryProperty(HealthTestUtil.INV_MAINTENANCE_FALSE,
                                               HealthTestUtil.INV_MAINTENANCE_TRUE);
        servicesStates = HealthTestUtil.connectToHealthEnpoint(503);
        checkStates(dataWhenInventoryDown, servicesStates);
    }

    private void checkStates(HashMap<String, String> testData, JsonArray servStates) {
        testData.forEach((service, expectedState) -> {
            assertEquals("The state of " + service + " service is not matching.",
                         expectedState,
                         HealthTestUtil.getActualState(service, servStates));
        });
    }

    @After
    public void teardown() {
        HealthTestUtil.cleanUp();
    }

}

Let’s break down the test cases:

  • The testIfServicesAreUp() test case compares the generated health report with the actual status of the services.

  • The testIfInventoryServiceIsDown() test cases puts the inventory service in maintenance by setting the io_openliberty_guides_inventory_inMaintenance property to true and then compares the generated health report with the actual status of the service.

A few more tests have been included to verify the basic functionalitiy of the system and inventory services. They can be found under the src/test/java/it/io/openliberty/guides/inventory/InventoryEndpointTest.java and src/test/java/it/io/openliberty/guides/system/SystemEndpointTest.java files. If a test failure occurs, then you might have introduced a bug into the code. These tests will run automatically as a part of the Maven build process when you run the mvn install command. You can also run these tests separately from the build by using the mvn verify command, but first make sure that the server is stopped.

Running the tests

If the server is still running from the previous steps, stop it using the Maven liberty:stop-server goal from command line in the start directory:

mvn liberty:stop-server

Then, verify that the tests pass using the Maven verify goal:

mvn verify

It may take some time before build is complete. If the tests pass, you will see a similar output to the following:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.health.HealthTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.504 sec - in it.io.openliberty.guides.health.HealthTest
Running it.io.openliberty.guides.inventory.InventoryEndpointTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.326 sec - in it.io.openliberty.guides.inventory.InventoryEndpointTest
Running it.io.openliberty.guides.system.SystemEndpointTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.011 sec - in it.io.openliberty.guides.system.SystemEndpointTest

Results :

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

To see whether the tests detect a failure, manually change the configuration of io_openliberty_guides_inventory_inMaintenance from false to true in the resources/CustomConfigSource.json file. Re-run the Maven build. You will see a test failure occur because the initial status of the inventory service is DOWN.

Great work! You’re done!

You just learned how to add health checks to report the states of microservices by using MicroProfile Health. Then, you wrote tests to validate the generated health report.

Feel free to try one of the related MicroProfile guides. They demonstrate additional technologies that you can learn and expand on top of what you built here.

Contribute to this guide

Is something missing or broken? Raise an issue, or send us a pull request.