Optimizing REST queries for microservices with GraphQL

duration 30 minutes

Learn how to use MicroProfile GraphQL to query and update data from multiple services, and how to test GraphQL queries and mutations using an interactive GraphQL tool (GraphiQL).

Prerequisites:

What you’ll learn

You will learn how to build and use a simple GraphQL service with MicroProfile GraphQL.

GraphQL is an open source data query language. Unlike REST APIs, each HTTP request that is sent to a GraphQL service goes to a single HTTP endpoint. Create, read, update, and delete operations and their details are differentiated by the contents of the request. If the operation returns data, the user specifies what properties of the data that they want returned. For read operations, a JSON object is returned that contains only the data and properties that are specified. For other operations, a JSON object might be returned containing information such as a success message.

Returning only the specified properties in a read operation has two benefits. If you’re dealing with large amounts of data or large resources, it reduces the size of the responses. If you have properties that are expensive to calculate or retrieve (such as nested objects), it also saves processing time. GraphQL calculates these properties only if they are requested.

A GraphQL service can also be used to obtain data from multiple sources such as APIs, databases, and other services. It can then collate this data into a single object for the user, simplifying the data retrieval. The user makes only a single request to the GraphQL service, instead of multiple requests to the individual data sources. GraphQL services require less data fetching than REST services, which results in lower application load times and lower data transfer costs. GraphQL also enables clients to better customize requests to the server.

All of the available operations to retrieve or modify data are available in a single GraphQL schema. The GraphQL schema describes all the data types that are used in the GraphQL service. The schema also describes all of the available operations. As well, you can add names and text descriptions to the various object types and operations in the schema.

You can learn more about GraphQL at the GraphQL website.

You’ll create a GraphQL application that retrieves data from multiple system services. Users make requests to the GraphQL service, which then makes requests to the system services. The GraphQL service returns a single JSON object containing all the system information from the system services.

GraphQL architecture where multiple system microservices are integrated behind one GraphQL service

You’ll enable the interactive GraphiQL tool in the Open Liberty runtime. GraphiQL helps you make queries to a GraphQL service. In the GraphiQL UI, you need to type only the body of the query for the purposes of manual tests and examples.

Additional prerequisites

Before you begin, Docker needs to be installed. For installation instructions, refer to the official Docker documentation. You’ll build and run the application in Docker containers.

Make sure to start your Docker daemon before you proceed.

Getting started

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

Copied to clipboard
git clone https://github.com/openliberty/guide-microprofile-graphql.git
cd guide-microprofile-graphql

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.

Creating GraphQL object types

Navigate to the start directory to begin.

Object types determine the structure of the data that GraphQL returns. These object types are defined by annotations that are applied to the declaration and properties of Java classes.

You will define java, systemMetrics, and systemInfo object types by creating and applying annotations to the JavaInfo, SystemMetrics, and SystemInfo classes respectively.

Create the JavaInfo class.
View Code
Copied to clipboard
models/src/main/java/io/openliberty/guides/graphql/models/JavaInfo.java

The JavaInfo class is annotated with a @Type annotation. The @Type("java") annotation maps this class to define the java object type in GraphQL. The java object type gives information on the Java installation of the system.

The @Description annotation gives a description to the java object type in GraphQL. This description is what appears in the schema and the documentation. Descriptions aren’t required, but it’s good practice to include them.

The @Name annotation maps the vendor property to the vendorName name of the java object type in GraphQL. The @Name annotation can be used to change the name of the property used in the schema. Without a @Name annotation, the Java object property is automatically mapped to a GraphQL object type property of the same name. In this case, without the @Name annotation, the property would be displayed as vendor in the schema.

All data types in GraphQL are nullable by default. Non-nullable properties are annotated with the @NonNull annotation. The @NonNull annotation on the version field ensures that, when queried, a non-null value is returned by the GraphQL service. The getVendor() and getVersion() getter functions are automatically mapped to retrieve their respective properties in GraphQL. If needed, setter functions are also supported and automatically mapped.

Create the SystemMetrics class.
View Code
Copied to clipboard
models/src/main/java/io/openliberty/guides/graphql/models/SystemMetrics.java

The SystemMetrics class is set up similarly. It maps to the systemMetrics object type, which describes system information such as the number of processor cores and the heap size.

Create the SystemInfo class.
View Code
Copied to clipboard
models/src/main/java/io/openliberty/guides/graphql/models/SystemInfo.java

The SystemInfo class is similar to the previous two classes. It maps to the system object type, which describes other information Java can retrieve from the system properties.

The java and systemMetrics object types are used as nested objects within the system object type. However, nested objects and other properties that are expensive to calculate or retrieve are not included in the class of an object type. Instead, expensive properties are added as part of implementing GraphQL resolvers.

To save time, the SystemLoad class and SystemLoadData class are provided for you. The SystemLoad class maps to the systemLoad object type, which describes the resource usage of a system service. The SystemLoadData class maps to the loadData object type. The loadData object will be a nested object inside the systemLoad object type. Together, these objects will contain the details of the resource usage of a system service.

Implementing system service

The system microservices are backend services that use Jakarta Restful Web Services. For more details on using Jakarta Restful Web Services, see the Creating a RESTful web service guide. These system microservices report system properties. GraphQL can access multiple instances of these system microservices and collate their information. In a real scenario, GraphQL might access multiple databases or other services.

Create the SystemPropertiesResource class.
View Code
Copied to clipboard
system/src/main/java/io/openliberty/guides/system/SystemPropertiesResource.java

The SystemPropertiesResource class provides endpoints to interact with the system properties. The properties/{property} endpoint accesses system properties. The properties/java endpoint assembles and returns an object describing the system’s Java installation. The note endpoint is used to write a note into the system properties.

Create the SystemMetricsResource class.
View Code
Copied to clipboard
system/src/main/java/io/openliberty/guides/system/SystemMetricsResource.java

The SystemMetricsResource class provides information on the system resources and their usage. The systemLoad endpoint assembles and returns an object that describes the system load. It includes the JVM heap load and processor load.

Implementing GraphQL resolvers

Resolvers are functions that provide instructions for GraphQL operations. Each operation requires a corresponding resolver. The query operation type is read-only and fetches data. The mutation operation type can create, delete, or modify data.

Create the GraphQLService class.
View Code
Copied to clipboard
graphql/src/main/java/io/openliberty/guides/graphql/GraphQLService.java

The resolvers are defined in the GraphQLService.java file. The @GraphQLApi annotation enables GraphQL to use the methods that are defined in this class as resolvers.

Operations of the query type are read-only operations that retrieve data. They’re defined by using the @Query annotation.

One of the query requests in this application is the system request. This request is handled by the getSystemInfo() function. It retrieves and bundles system information into a SystemInfo object that is returned.

It uses a @Name on one of its input parameters. The @Name annotation has different functions depending on the context in which it’s used. In this context, it denotes input parameters for GraphQL operations. For the getSystemInfo() function, it’s used to input the hostname for the system you want to look up information for.

Recall that the SystemInfo class contained nested objects. It contained a JavaInfo and an SystemMetrics object. The @Source annotation is used to add these nested objects as properties to the SystemInfo object.

The @Name appears again here. In this context alongside the @Source annotation, it’s used to connect the java and systemMetrics object types to system requests and the system object type.

The other query request is the systemLoad request, which is handled by the getSystemLoad() function. The systemLoad request retrieves information about the resource usage of any number of system services. It accepts an array of hostnames as the input for the systems to look up. It’s set up similarly to the system request, with the loadData function used for the nested SystemLoadData object.

Operations of the mutation type are used to edit data. They can create, update, or delete data. They’re defined by using the @Mutation annotation.

There’s one mutation operation in this application - the editNote request. This request is handled by the editNote() function. This request is used to write a note into the properties of a given system. There are inputs for the system you want to write into, and the note you want to write.

Each resolver function has a @Description annotation, which provides a description that is used for the schema. Descriptions aren’t required, but it’s good practice to include them.

Enabling GraphQL

To use GraphQL, the MicroProfile GraphQL dependencies and features need to be included.

Replace the Maven project file.
View Code
Copied to clipboard
graphql/pom.xml

Adding the microprofile-graphql-api dependency to the pom.xml enables the GraphQL annotations that are used to develop the application.

The Open Liberty needs to be configured to support the GraphQL query language.

Replace the Liberty server.xml configuration file.
View Code
Copied to clipboard
graphql/src/main/liberty/config/server.xml

The mpGraphQL feature that is added to the server.xml enables the use of the MicroProfile GraphQL feature in Open Liberty. Open Liberty’s MicroProfile GraphQL feature includes GraphiQL. Enable it by setting the io.openliberty.enableGraphQLUI variable to true.

Building and running the application

From the start directory, run the following commands:

Copied to clipboard
mvn -pl models install
mvn package

The mvn install command compiles and packages the object types you created to a .jar file. This allows them to be used by the system and graphql services. The mvn package command packages the system and graphql services to .war files.

Dockerfiles have already been set up for you. Build your Docker images with the following commands:

Copied to clipboard
docker build -t system:1.0-java11-SNAPSHOT --build-arg JAVA_VERSION=java11 system/.
docker build -t system:1.0-java17-SNAPSHOT --build-arg JAVA_VERSION=java17 system/.
docker build -t graphql:1.0-SNAPSHOT graphql/.

The --build-arg parameter is used to create two different system services. One uses Java 11, while the other uses Java 17. Run these Docker images using the provided startContainers script. The script creates a network for the services to communicate through. It creates two system services and a GraphQL service.

Copied to clipboard
.\scripts\startContainers.bat

The containers may take some time to become available.

Running GraphQL queries

Before you make any requests, see the http://localhost:9082/graphql/schema.graphql URL. This URL returns the schema that describes the GraphQL service.

To access the GraphQL service, GraphiQL has already been set up and included for you. Access GraphiQL at the http://localhost:9082/graphql-ui URL.

Queries that are made through GraphiQL are the same as queries that are made through HTTP requests. You can also view the schema through GraphiQL by clicking the Docs button on the menu bar.

Run the following query operation in GraphiQL to get every system property from the container running on Java 11:

Copied to clipboard
query {
  system(hostname: "system-java11") {
    hostname
    username
    osArch
    osName
    osVersion
    systemMetrics {
      processors
      heapSize
      nonHeapSize
    }
    java {
      vendorName
      version
    }
  }
}

The output is similar to the following example:

{
  "data": {
    "system": {
      "hostname": "system-java11",
      "username": "default",
      "osArch": "amd64",
      "osName": "Linux",
      "osVersion": "5.10.25-linuxkit",
      "systemMetrics": {
        "processors": 4,
        "heapSize": 1031864320,
        "nonHeapSize": -1
      },
      "java": {
        "vendorName": "AdoptOpenJDK",
        "version": "11.0.18"
      }
    }
  }
}

Run the following mutation operation to add a note to the system service running on Java 11:

Copied to clipboard
mutation {
  editNote(
    hostname: "system-java11"
    note: "I'm trying out GraphQL on Open Liberty!"
  )
}

You receive a response containing the Boolean true to let you know that the request was successfully processed. You can see the note that you added by running the following query operation. Notice that there’s no need to run a full query, as you only want the note property. Thus, the request only contains the note property.

Copied to clipboard
query {
  system(hostname: "system-java11") {
    note
  }
}

The response is similar to the following example:

{
  "data": {
    "system": {
      "note": "I'm trying out GraphQL on Open Liberty!"
    }
  }
}

GraphQL returns only the note property, as it was the only property in the request. You can try out the operations using the hostname system-java17 as well. To see an example of using an array as an input for an operation, try the following operation to get system loads:

Copied to clipboard
query {
  systemLoad(hostnames: ["system-java11", "system-java17"]) {
    hostname
    loadData {
      heapUsed
      nonHeapUsed
      loadAverage
    }
  }
}

The response is similar to the following example:

{
  "data": {
    "systemLoad": [
      {
        "hostname": "system-java11",
        "loadData": {
          "heapUsed": 32432048,
          "nonHeapUsed": 85147084,
          "loadAverage": 0.36
        }
      },
      {
        "hostname": "system-java17",
        "loadData": {
          "heapUsed": 39373688,
          "nonHeapUsed": 90736300,
          "loadAverage": 0.36
        }
      }
    ]
  }
}

Tearing down the environment

When you’re done checking out the application, run the following script to stop the application:

Copied to clipboard
.\scripts\stopContainers.bat
12package io.openliberty.guides.graphql.models;
13
14import org.eclipse.microprofile.graphql.Description;
15import org.eclipse.microprofile.graphql.Name;
16import org.eclipse.microprofile.graphql.NonNull;
17import org.eclipse.microprofile.graphql.Type;
18
20@Type("java")
23@Description("Information about a Java installation")
26public class JavaInfo {
27
29    @Name("vendorName")
31    private String vendor;
32
34    @NonNull
37    private String version;
39
41    public String getVendor() {
42        return this.vendor;
43    }
45
46    public void setVendor(String vendor) {
47        this.vendor = vendor;
48    }
49
51    public String getVersion() {
52        return this.version;
53    }
55
56    public void setVersion(String version) {
57        this.version = version;
58    }
59
60}
62

Prerequisites:

Nice work! Where to next?

Nice work! You just created a basic GraphQL service using MicroProfile GraphQL in Open Liberty!

Optimizing REST queries for microservices with GraphQL by Open Liberty is licensed under CC BY-ND 4.0

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