Contents
Tags
Optimizing REST queries for microservices with GraphQL
Prerequisites:
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.
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:
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 theJavaInfo
class.models/src/main/java/io/openliberty/guides/graphql/models/JavaInfo.java
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 theSystemMetrics
class.models/src/main/java/io/openliberty/guides/graphql/models/SystemMetrics.java
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 theSystemInfo
class.models/src/main/java/io/openliberty/guides/graphql/models/SystemInfo.java
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.
SystemLoad.java
SystemLoadData.java
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 theSystemPropertiesResource
class.system/src/main/java/io/openliberty/guides/system/SystemPropertiesResource.java
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 theSystemMetricsResource
class.system/src/main/java/io/openliberty/guides/system/SystemMetricsResource.java
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 theGraphQLService
class.graphql/src/main/java/io/openliberty/guides/graphql/GraphQLService.java
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.
graphql/pom.xml
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.
graphql/src/main/liberty/config/server.xml
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:
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.
WINDOWS
MAC
LINUX
.\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:
WINDOWS
MAC
LINUX
.\scripts\stopContainers.bat
./scripts/stopContainers.sh
Great work! You’re done!
You just created a basic GraphQL service using MicroProfile GraphQL in Open Liberty!
Guide Attribution
Optimizing REST queries for microservices with GraphQL by Open Liberty is licensed under CC BY-ND 4.0
Prerequisites:
Nice work! Where to next?
What did you think of this guide?
Thank you for your feedback!
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