Optimizing REST queries for microservices with GraphQL

duration 30 minutes

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.

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:

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

JavaInfo.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2021 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.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
19// tag::type[]
20@Type("java")
21// end::type[]
22// tag::description[]
23@Description("Information about a Java installation")
24// end::description[]
25// tag::class[]
26public class JavaInfo {
27
28    // tag::name[]
29    @Name("vendorName")
30    // end::name[]
31    private String vendor;
32
33    // tag::nonnull[]
34    @NonNull
35    // end::nonnull[]
36    // tag::version[]
37    private String version;
38    // end::version[]
39
40    // tag::getVendor[]
41    public String getVendor() {
42        return this.vendor;
43    }
44    // end::getVendor[]
45
46    public void setVendor(String vendor) {
47        this.vendor = vendor;
48    }
49
50    // tag::getVersion[]
51    public String getVersion() {
52        return this.version;
53    }
54    // end::getVersion[]
55
56    public void setVersion(String version) {
57        this.version = version;
58    }
59
60}
61// end::class[]

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

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2021 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.graphql.models;
13
14import org.eclipse.microprofile.graphql.Description;
15import org.eclipse.microprofile.graphql.NonNull;
16import org.eclipse.microprofile.graphql.Type;
17
18// tag::type[]
19@Type("systemMetrics")
20// end::type[]
21// tag::description[]
22@Description("System metrics")
23// end::description[]
24// tag::class[]
25public class SystemMetrics {
26
27    @NonNull
28    private Integer processors;
29
30    @NonNull
31    private Long heapSize;
32
33    @NonNull
34    private Long nonHeapSize;
35
36    public Integer getProcessors() {
37        return processors;
38    }
39
40    public void setProcessors(int processors) {
41        this.processors = processors;
42    }
43
44    public Long getHeapSize() {
45        return heapSize;
46    }
47
48    public void setHeapSize(long heapSize) {
49        this.heapSize = heapSize;
50    }
51
52    public Long getNonHeapSize() {
53        return nonHeapSize;
54    }
55
56    public void setNonHeapSize(Long nonHeapSize) {
57        this.nonHeapSize = nonHeapSize;
58    }
59
60}
61// end::class[]

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

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2021 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.graphql.models;
 13
 14import org.eclipse.microprofile.graphql.Description;
 15import org.eclipse.microprofile.graphql.NonNull;
 16import org.eclipse.microprofile.graphql.Type;
 17
 18// tag::type[]
 19@Type("system")
 20// end::type[]
 21@Description("Information about a single system")
 22// tag::class[]
 23public class SystemInfo {
 24
 25    @NonNull
 26    private String hostname;
 27
 28    @NonNull
 29    private String username;
 30
 31    private String osName;
 32    private String osArch;
 33    private String osVersion;
 34    private String note;
 35
 36    // tag::java[]
 37    private JavaInfo java;
 38    // end::java[]
 39
 40    // tag::metrics[]
 41    private SystemMetrics systemMetrics;
 42    // end::metrics[]
 43
 44    public String getHostname() {
 45        return this.hostname;
 46    }
 47
 48    public void setHostname(String hostname) {
 49        this.hostname = hostname;
 50    }
 51
 52    public String getUsername() {
 53        return this.username;
 54    }
 55
 56    public void setUsername(String username) {
 57        this.username = username;
 58    }
 59
 60    public String getOsName() {
 61        return osName;
 62    }
 63
 64    public void setOsName(String osName) {
 65        this.osName = osName;
 66    }
 67
 68    public String getOsArch() {
 69        return osArch;
 70    }
 71
 72    public void setOsArch(String osarch) {
 73        this.osArch = osarch;
 74    }
 75
 76    public String getOsVersion() {
 77        return osVersion;
 78    }
 79
 80    public void setOsVersion(String osVersion) {
 81        this.osVersion = osVersion;
 82    }
 83
 84    public String getNote() {
 85        return this.note;
 86    }
 87
 88    public void setNote(String note) {
 89        this.note = note;
 90    }
 91
 92    public JavaInfo getJava() {
 93        return java;
 94    }
 95
 96    public void setJava(JavaInfo java) {
 97        this.java = java;
 98    }
 99
100    public SystemMetrics getSystemMetrics() {
101        return systemMetrics;
102    }
103
104    public void setSystemMetrics(SystemMetrics systemMetrics) {
105        this.systemMetrics = systemMetrics;
106    }
107
108}
109// end::class[]

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

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2021 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.graphql.models;
13
14import org.eclipse.microprofile.graphql.Description;
15import org.eclipse.microprofile.graphql.NonNull;
16import org.eclipse.microprofile.graphql.Type;
17
18// tag::type[]
19@Type("systemLoad")
20// end::type[]
21// tag::description[]
22@Description("Information of system usage")
23// end::description[]
24// tag::class[]
25public class SystemLoad {
26
27    @NonNull
28    private String hostname;
29
30    @NonNull
31    private SystemLoadData loadData;
32
33    public String getHostname() {
34        return this.hostname;
35    }
36
37    public void setHostname(String hostname) {
38        this.hostname = hostname;
39    }
40
41    public SystemLoadData getLoadData() {
42        return this.loadData;
43    }
44
45    public void setLoadData(SystemLoadData loadData) {
46        this.loadData = loadData;
47    }
48
49}
50// end::class[]

SystemLoadData.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2021 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.graphql.models;
13
14import org.eclipse.microprofile.graphql.Description;
15import org.eclipse.microprofile.graphql.NonNull;
16import org.eclipse.microprofile.graphql.Type;
17
18// tag::type[]
19@Type("loadData")
20// end::type[]
21// tag::description[]
22@Description("System usage data")
23// end::description[]
24// tag::class[]
25public class SystemLoadData {
26
27    @NonNull
28    private Double loadAverage;
29
30    @NonNull
31    private Long heapUsed;
32
33    @NonNull
34    private Long nonHeapUsed;
35
36    public Double getLoadAverage() {
37        return loadAverage;
38    }
39
40    public void setLoadAverage(Double loadAverage) {
41        this.loadAverage = loadAverage;
42    }
43
44    public Long getHeapUsed() {
45        return heapUsed;
46    }
47
48    public void setHeapUsed(Long heapUsed) {
49        this.heapUsed = heapUsed;
50    }
51
52    public Long getNonHeapUsed() {
53        return nonHeapUsed;
54    }
55
56    public void setNonHeapUsed(Long nonHeapUsed) {
57        this.nonHeapUsed = nonHeapUsed;
58    }
59
60}
61// end::class[]

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

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2021, 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.system;
13
14import jakarta.enterprise.context.ApplicationScoped;
15import jakarta.ws.rs.Consumes;
16import jakarta.ws.rs.GET;
17import jakarta.ws.rs.POST;
18import jakarta.ws.rs.Path;
19import jakarta.ws.rs.PathParam;
20import jakarta.ws.rs.Produces;
21import jakarta.ws.rs.core.MediaType;
22import jakarta.ws.rs.core.Response;
23
24import io.openliberty.guides.graphql.models.JavaInfo;
25
26@ApplicationScoped
27@Path("/")
28public class SystemPropertiesResource {
29
30    // tag::queryProperty[]
31    @GET
32    @Path("properties/{property}")
33    @Produces(MediaType.TEXT_PLAIN)
34    public String queryProperty(@PathParam("property") String property) {
35        return System.getProperty(property);
36    }
37    // end::queryProperty[]
38
39    // tag::java[]
40    @GET
41    @Path("properties/java")
42    @Produces(MediaType.APPLICATION_JSON)
43    public JavaInfo java() {
44        JavaInfo javaInfo = new JavaInfo();
45        javaInfo.setVersion(System.getProperty("java.version"));
46        javaInfo.setVendor(System.getProperty("java.vendor"));
47        return javaInfo;
48    }
49    // end::java[]
50
51    // tag::note[]
52    @POST
53    @Path("note")
54    @Consumes(MediaType.TEXT_PLAIN)
55    @Produces(MediaType.APPLICATION_JSON)
56    public Response editNote(String text) {
57        System.setProperty("note", text);
58        return Response.ok().build();
59    }
60    // end::note[]
61
62}

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

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2021, 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.system;
13
14import java.lang.management.ManagementFactory;
15import java.lang.management.MemoryMXBean;
16import java.lang.management.OperatingSystemMXBean;
17
18import jakarta.enterprise.context.ApplicationScoped;
19import jakarta.ws.rs.GET;
20import jakarta.ws.rs.Path;
21import jakarta.ws.rs.Produces;
22import jakarta.ws.rs.core.MediaType;
23
24import io.openliberty.guides.graphql.models.SystemLoadData;
25import io.openliberty.guides.graphql.models.SystemMetrics;
26
27@ApplicationScoped
28@Path("metrics")
29public class SystemMetricsResource {
30
31    private static final OperatingSystemMXBean OS_MEAN =
32                             ManagementFactory.getOperatingSystemMXBean();
33
34    private static final MemoryMXBean MEM_BEAN = ManagementFactory.getMemoryMXBean();
35
36    // tag::systemMetrics[]
37    @GET
38    @Produces(MediaType.APPLICATION_JSON)
39    public SystemMetrics getSystemMetrics() {
40        SystemMetrics metrics = new SystemMetrics();
41        metrics.setProcessors(OS_MEAN.getAvailableProcessors());
42        metrics.setHeapSize(MEM_BEAN.getHeapMemoryUsage().getMax());
43        metrics.setNonHeapSize(MEM_BEAN.getNonHeapMemoryUsage().getMax());
44        return metrics;
45    }
46    // end::systemMetrics[]
47
48    // tag::systemLoad[]
49    @GET
50    @Path("/systemLoad")
51    @Produces(MediaType.APPLICATION_JSON)
52    public SystemLoadData getSystemLoad() {
53        SystemLoadData systemLoadData = new SystemLoadData();
54        systemLoadData.setLoadAverage(OS_MEAN.getSystemLoadAverage());
55        systemLoadData.setHeapUsed(MEM_BEAN.getHeapMemoryUsage().getUsed());
56        systemLoadData.setNonHeapUsed(MEM_BEAN.getNonHeapMemoryUsage().getUsed());
57        return systemLoadData;
58    }
59    // end::systemLoad[]
60}

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

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2021, 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.graphql;
 13
 14import java.net.URI;
 15import java.util.ArrayList;
 16import java.util.Collections;
 17import java.util.HashMap;
 18import java.util.List;
 19import java.util.Map;
 20
 21import jakarta.inject.Inject;
 22import jakarta.ws.rs.ProcessingException;
 23
 24import org.eclipse.microprofile.config.inject.ConfigProperty;
 25import org.eclipse.microprofile.graphql.Description;
 26import org.eclipse.microprofile.graphql.GraphQLApi;
 27import org.eclipse.microprofile.graphql.Mutation;
 28import org.eclipse.microprofile.graphql.Name;
 29import org.eclipse.microprofile.graphql.NonNull;
 30import org.eclipse.microprofile.graphql.Query;
 31import org.eclipse.microprofile.graphql.Source;
 32import org.eclipse.microprofile.rest.client.RestClientBuilder;
 33
 34import io.openliberty.guides.graphql.client.SystemClient;
 35import io.openliberty.guides.graphql.client.UnknownUriException;
 36import io.openliberty.guides.graphql.client.UnknownUriExceptionMapper;
 37import io.openliberty.guides.graphql.models.JavaInfo;
 38import io.openliberty.guides.graphql.models.SystemInfo;
 39import io.openliberty.guides.graphql.models.SystemLoad;
 40import io.openliberty.guides.graphql.models.SystemLoadData;
 41import io.openliberty.guides.graphql.models.SystemMetrics;
 42
 43// tag::graphqlapi[]
 44@GraphQLApi
 45// end::graphqlapi[]
 46public class GraphQLService {
 47
 48    private static Map<String, SystemClient> clients =
 49            Collections.synchronizedMap(new HashMap<String, SystemClient>());
 50
 51    @Inject
 52    @ConfigProperty(name = "system.http.port", defaultValue = "9080")
 53    String SYSTEM_PORT;
 54
 55    // tag::query1[]
 56    @Query("system")
 57    // end::query1[]
 58    // tag::nonnull1[]
 59    @NonNull
 60    // end::nonnull1[]
 61    // tag::description1[]
 62    @Description("Gets information about the system")
 63    // end::description1[]
 64    // tag::getSystemInfo[]
 65    // tag::getSystemInfoHeader[]
 66    public SystemInfo getSystemInfo(@Name("hostname") String hostname)
 67        throws ProcessingException, UnknownUriException {
 68    // end::getSystemInfoHeader[]
 69        SystemClient systemClient = getSystemClient(hostname);
 70        SystemInfo systemInfo = new SystemInfo();
 71        systemInfo.setHostname(hostname);
 72        systemInfo.setUsername(systemClient.queryProperty("user.name"));
 73        systemInfo.setOsName(systemClient.queryProperty("os.name"));
 74        systemInfo.setOsArch(systemClient.queryProperty("os.arch"));
 75        systemInfo.setOsVersion(systemClient.queryProperty("os.version"));
 76        systemInfo.setNote(systemClient.queryProperty("note"));
 77
 78        return systemInfo;
 79    }
 80    // end::getSystemInfo[]
 81
 82    // tag::mutation[]
 83    @Mutation("editNote")
 84    // end::mutation[]
 85    // tag::description2[]
 86    @Description("Changes the note set for the system")
 87    // end::description2[]
 88    // tag::editNoteFunction[]
 89    // tag::editNoteHeader[]
 90    public boolean editNote(@Name("hostname") String hostname,
 91                            @Name("note") String note)
 92        throws ProcessingException, UnknownUriException {
 93    // end::editNoteHeader[]
 94        SystemClient systemClient = getSystemClient(hostname);
 95        systemClient.editNote(note);
 96        return true;
 97    }
 98    // end::editNoteFunction[]
 99
100    // tag::query2[]
101    @Query("systemLoad")
102    // end::query2[]
103    // tag::description3[]
104    @Description("Gets system load data from the systems")
105    // end::description3[]
106    // tag::getSystemLoad[]
107    // tag::getSystemLoadHeader[]
108    public SystemLoad[] getSystemLoad(@Name("hostnames") String[] hostnames)
109        throws ProcessingException, UnknownUriException {
110    // end::getSystemLoadHeader[]
111        if (hostnames == null || hostnames.length == 0) {
112            return new SystemLoad[0];
113        }
114
115        List<SystemLoad> systemLoads = new ArrayList<SystemLoad>(hostnames.length);
116
117        for (String hostname : hostnames) {
118            SystemLoad systemLoad = new SystemLoad();
119            systemLoad.setHostname(hostname);
120            systemLoads.add(systemLoad);
121        }
122
123        return systemLoads.toArray(new SystemLoad[systemLoads.size()]);
124    }
125    // end::getSystemLoad[]
126
127    // Nested objects, these can be expensive to obtain
128    // tag::nonnull3[]
129    @NonNull
130    // tag::nonnull3[]
131    // tag::systemMetricsFunction[]
132    // tag::systemMetricsHeader[]
133    public SystemMetrics systemMetrics(
134        @Source @Name("system") SystemInfo systemInfo)
135        throws ProcessingException, UnknownUriException {
136    // end::systemMetricsHeader[]
137        String hostname = systemInfo.getHostname();
138        SystemClient systemClient = getSystemClient(hostname);
139        return systemClient.getSystemMetrics();
140    }
141    // end::systemMetricsFunction[]
142
143    // tag::nonnull4[]
144    @NonNull
145    // end::nonnull4[]
146    // tag::javaFunction[]
147    // tag::javaHeader[]
148    public JavaInfo java(@Source @Name("system") SystemInfo systemInfo)
149        throws ProcessingException, UnknownUriException {
150    // end::javaHeader[]
151        String hostname = systemInfo.getHostname();
152        SystemClient systemClient = getSystemClient(hostname);
153        return systemClient.java();
154    }
155    // end::javaFunction[]
156
157    // tag::loadData[]
158    // tag::loadDataHeader[]
159    public SystemLoadData loadData(@Source @Name("systemLoad") SystemLoad systemLoad)
160        throws ProcessingException, UnknownUriException {
161    // end::loadDataHeader[]
162        String hostname = systemLoad.getHostname();
163        SystemClient systemClient = getSystemClient(hostname);
164        return systemClient.getSystemLoad();
165    }
166    // end::loadData[]
167
168    private SystemClient getSystemClient(String hostname) {
169        SystemClient sc = clients.get(hostname);
170        if (sc == null) {
171            String customURIString = "http://" + hostname + ":"
172                                      + SYSTEM_PORT + "/system";
173            URI customURI = URI.create(customURIString);
174            sc = RestClientBuilder
175                   .newBuilder()
176                   .baseUri(customURI)
177                   .register(UnknownUriExceptionMapper.class)
178                   .build(SystemClient.class);
179            clients.put(hostname, sc);
180        }
181        return sc;
182    }
183}

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

  1<?xml version='1.0' encoding='utf-8'?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0"
  3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5    <modelVersion>4.0.0</modelVersion>
  6
  7    <groupId>io.openliberty.guides</groupId>
  8    <artifactId>guide-microprofile-graphql-graphql</artifactId>
  9    <version>1.0-SNAPSHOT</version>
 10    <packaging>war</packaging>
 11
 12    <properties>
 13        <maven.compiler.source>11</maven.compiler.source>
 14        <maven.compiler.target>11</maven.compiler.target>
 15        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 16        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 17        <!-- Liberty configuration -->
 18        <liberty.var.http.port>9082</liberty.var.http.port>
 19        <liberty.var.https.port>9445</liberty.var.https.port>
 20    </properties>
 21
 22    <dependencies>
 23        <!-- Provided dependencies -->
 24        <dependency>
 25            <groupId>jakarta.platform</groupId>
 26            <artifactId>jakarta.jakartaee-api</artifactId>
 27            <version>10.0.0</version>
 28            <scope>provided</scope>
 29        </dependency>
 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        
 38        <!-- Required dependencies -->
 39        <!-- tag::models[] -->
 40        <dependency>
 41           <groupId>io.openliberty.guides</groupId>
 42           <artifactId>guide-microprofile-graphql-models</artifactId>
 43           <version>1.0-SNAPSHOT</version>
 44        </dependency>
 45        <!-- end::models[] -->
 46        
 47        <!-- GraphQL API dependencies -->
 48        <!-- tag::graphQLDependency[] -->
 49        <dependency>
 50            <groupId>org.eclipse.microprofile.graphql</groupId>
 51            <artifactId>microprofile-graphql-api</artifactId>
 52            <version>2.0</version>
 53            <scope>provided</scope>
 54        </dependency>
 55        <!-- end::graphQLDependency[] -->
 56        <!-- For tests -->
 57        <dependency>
 58            <groupId>org.junit.jupiter</groupId>
 59            <artifactId>junit-jupiter</artifactId>
 60            <version>5.10.1</version>
 61            <scope>test</scope>
 62        </dependency>
 63        <dependency>
 64            <groupId>org.jboss.resteasy</groupId>
 65            <artifactId>resteasy-client</artifactId>
 66            <version>6.2.7.Final</version>
 67            <scope>test</scope>
 68        </dependency>
 69        <dependency>
 70            <groupId>org.jboss.resteasy</groupId>
 71            <artifactId>resteasy-json-binding-provider</artifactId>
 72            <version>6.2.7.Final</version>
 73            <scope>test</scope>
 74        </dependency>
 75        <dependency>
 76            <groupId>org.glassfish</groupId>
 77            <artifactId>jakarta.json</artifactId>
 78            <version>2.0.1</version>
 79            <scope>test</scope>
 80        </dependency>
 81    </dependencies>
 82
 83    <build>
 84        <finalName>${project.artifactId}</finalName>
 85        <plugins>
 86            <!-- Enable liberty-maven plugin -->
 87            <plugin>
 88                <groupId>io.openliberty.tools</groupId>
 89                <artifactId>liberty-maven-plugin</artifactId>
 90                <version>3.10</version>
 91                <configuration>
 92                    <looseApplication>false</looseApplication>
 93                </configuration>
 94            </plugin>
 95            <plugin>
 96                <groupId>org.apache.maven.plugins</groupId>
 97                <artifactId>maven-war-plugin</artifactId>
 98                <version>3.3.2</version>
 99            </plugin>
100            <plugin>
101                <groupId>org.apache.maven.plugins</groupId>
102                <artifactId>maven-surefire-plugin</artifactId>
103                <version>3.2.5</version>
104            </plugin>
105            <!-- Plugin to run functional tests -->
106            <plugin>
107                <groupId>org.apache.maven.plugins</groupId>
108                <artifactId>maven-failsafe-plugin</artifactId>
109                <version>3.2.5</version>
110                <configuration>
111                    <systemPropertyVariables>
112                        <http.port>${liberty.var.http.port}</http.port>
113                    </systemPropertyVariables>
114                </configuration>
115            </plugin>
116        </plugins>
117    </build>
118</project>

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

 1<server description="GraphQL service">
 2    <featureManager>
 3        <feature>restfulWS-3.1</feature>
 4        <feature>jsonb-3.0</feature>
 5        <feature>jsonp-2.1</feature>
 6        <feature>cdi-4.0</feature>
 7        <feature>mpConfig-3.1</feature>
 8        <feature>mpRestClient-3.0</feature>
 9        <!-- tag::graphql[] -->
10        <feature>mpGraphQL-2.0</feature>
11        <!-- end::graphql[] -->
12    </featureManager>
13
14    <variable name="http.port" defaultValue="9082"/>
15    <variable name="https.port" defaultValue="9445"/>
16
17    <!-- tag::enableGraphiql[] -->
18    <variable name="io.openliberty.enableGraphQLUI" value="true" />
19    <!-- end::enableGraphiql[] -->
20
21    <webApplication location="guide-microprofile-graphql-graphql.war" contextRoot="/" />
22    <httpEndpoint host="*" httpPort="${http.port}" 
23        httpsPort="${https.port}" id="defaultHttpEndpoint"/>
24</server>

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!

Guide Attribution

Optimizing REST queries for microservices with GraphQL 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