Skip to content

OpenLiberty/guide-microprofile-graphql

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Optimizing REST queries for microservices with GraphQL

Note
This repository contains the guide documentation source. To view the guide in published form, view it on the Open Liberty website.

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).

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.

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.
models/src/main/java/io/openliberty/guides/graphql/models/JavaInfo.java

JavaInfo.java

link:finish/models/src/main/java/io/openliberty/guides/graphql/models/JavaInfo.java[role=include]

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.
models/src/main/java/io/openliberty/guides/graphql/models/SystemMetrics.java

SystemMetrics.java

link:finish/models/src/main/java/io/openliberty/guides/graphql/models/SystemMetrics.java[role=include]

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.
models/src/main/java/io/openliberty/guides/graphql/models/SystemInfo.java

SystemInfo.java

link:finish/models/src/main/java/io/openliberty/guides/graphql/models/SystemInfo.java[role=include]

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.

SystemLoad.java

link:finish/models/src/main/java/io/openliberty/guides/graphql/models/SystemLoad.java[role=include]

SystemLoadData.java

link:finish/models/src/main/java/io/openliberty/guides/graphql/models/SystemLoadData.java[role=include]

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.
system/src/main/java/io/openliberty/guides/system/SystemPropertiesResource.java

SystemPropertiesResource.java

link:finish/system/src/main/java/io/openliberty/guides/system/SystemPropertiesResource.java[role=include]

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.
system/src/main/java/io/openliberty/guides/system/SystemMetricsResource.java

SystemMetricsResource.java

link:finish/system/src/main/java/io/openliberty/guides/system/SystemMetricsResource.java[role=include]

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.
graphql/src/main/java/io/openliberty/guides/graphql/GraphQLService.java

GraphQLService.java

link:finish/graphql/src/main/java/io/openliberty/guides/graphql/GraphQLService.java[role=include]

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.
graphql/pom.xml

pom.xml

link:finish/graphql/pom.xml[role=include]

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.
graphql/src/main/liberty/config/server.xml

server.xml

link:finish/graphql/src/main/liberty/config/server.xml[role=include]

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:

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:

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.

.\scripts\startContainers.bat
./scripts/startContainers.sh

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:

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:

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.

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:

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:

.\scripts\stopContainers.bat
./scripts/stopContainers.sh

Great work! You’re done!

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

About

A guide on 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).

Resources

License

Stars

Watchers

Forks

Packages

No packages published