Optimizing REST queries for microservices with GraphQL

duration 30 minutes
New

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

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

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

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.

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 v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.graphql.models;
14
15import org.eclipse.microprofile.graphql.Description;
16import org.eclipse.microprofile.graphql.Name;
17import org.eclipse.microprofile.graphql.NonNull;
18import org.eclipse.microprofile.graphql.Type;
19
20// tag::type[]
21@Type("java")
22// end::type[]
23// tag::description[]
24@Description("Information about a Java installation")
25// end::description[]
26// tag::class[]
27public class JavaInfo {
28
29    // tag::name[]
30    @Name("vendorName")
31    // end::name[]
32    private String vendor;
33
34    // tag::nonnull[]
35    @NonNull
36    // end::nonnull[]
37    // tag::version[]
38    private String version;
39    // end::version[]
40
41    // tag::getVendor[]
42    public String getVendor() {
43        return this.vendor;
44    }
45    // end::getVendor[]
46
47    public void setVendor(String vendor) {
48        this.vendor = vendor;
49    }
50
51    // tag::getVersion[]
52    public String getVersion() {
53        return this.version;
54    }
55    // end::getVersion[]
56
57    public void setVersion(String version) {
58        this.version = version;
59    }
60
61}
62// end::class[]

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 v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.graphql.models;
14
15import org.eclipse.microprofile.graphql.Description;
16import org.eclipse.microprofile.graphql.NonNull;
17import org.eclipse.microprofile.graphql.Type;
18
19// tag::type[]
20@Type("systemMetrics")
21// end::type[]
22// tag::description[]
23@Description("System metrics")
24// end::description[]
25// tag::class[]
26public class SystemMetrics {
27
28    @NonNull
29    private Integer processors;
30
31    @NonNull
32    private Long heapSize;
33
34    @NonNull
35    private Long nonHeapSize;
36
37    public Integer getProcessors() {
38        return processors;
39    }
40
41    public void setProcessors(int processors) {
42        this.processors = processors;
43    }
44
45    public Long getHeapSize() {
46        return heapSize;
47    }
48
49    public void setHeapSize(long heapSize) {
50        this.heapSize = heapSize;
51    }
52
53    public Long getNonHeapSize() {
54        return nonHeapSize;
55    }
56
57    public void setNonHeapSize(Long nonHeapSize) {
58        this.nonHeapSize = nonHeapSize;
59    }
60
61}
62// end::class[]

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

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 v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.graphql.models;
14
15import org.eclipse.microprofile.graphql.Description;
16import org.eclipse.microprofile.graphql.NonNull;
17import org.eclipse.microprofile.graphql.Type;
18
19// tag::type[]
20@Type("systemLoad")
21// end::type[]
22// tag::description[]
23@Description("Information of system usage")
24// end::description[]
25// tag::class[]
26public class SystemLoad {
27
28    @NonNull
29    private String hostname;
30
31    @NonNull
32    private SystemLoadData loadData;
33
34    public String getHostname() {
35        return this.hostname;
36    }
37
38    public void setHostname(String hostname) {
39        this.hostname = hostname;
40    }
41
42    public SystemLoadData getLoadData() {
43        return this.loadData;
44    }
45
46    public void setLoadData(SystemLoadData loadData) {
47        this.loadData = loadData;
48    }
49
50}
51// 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 v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.graphql.models;
14
15import org.eclipse.microprofile.graphql.Description;
16import org.eclipse.microprofile.graphql.NonNull;
17import org.eclipse.microprofile.graphql.Type;
18
19// tag::type[]
20@Type("loadData")
21// end::type[]
22// tag::description[]
23@Description("System usage data")
24// end::description[]
25// tag::class[]
26public class SystemLoadData {
27
28    @NonNull
29    private Double loadAverage;
30
31    @NonNull
32    private Long heapUsed;
33
34    @NonNull
35    private Long nonHeapUsed;
36
37    public Double getLoadAverage() {
38        return loadAverage;
39    }
40
41    public void setLoadAverage(Double loadAverage) {
42        this.loadAverage = loadAverage;
43    }
44
45    public Long getHeapUsed() {
46        return heapUsed;
47    }
48
49    public void setHeapUsed(Long heapUsed) {
50        this.heapUsed = heapUsed;
51    }
52
53    public Long getNonHeapUsed() {
54        return nonHeapUsed;
55    }
56
57    public void setNonHeapUsed(Long nonHeapUsed) {
58        this.nonHeapUsed = nonHeapUsed;
59    }
60
61}
62// end::class[]

Implementing system service

The system microservices are backend services that use JAX-RS. For more details on using JAX-RS, 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

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

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.

SystemPropertiesResource.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 v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.system;
14
15import javax.enterprise.context.ApplicationScoped;
16import javax.ws.rs.Consumes;
17import javax.ws.rs.GET;
18import javax.ws.rs.POST;
19import javax.ws.rs.Path;
20import javax.ws.rs.PathParam;
21import javax.ws.rs.Produces;
22import javax.ws.rs.core.MediaType;
23import javax.ws.rs.core.Response;
24
25import io.openliberty.guides.graphql.models.JavaInfo;
26
27@ApplicationScoped
28@Path("/")
29public class SystemPropertiesResource {
30
31    // tag::queryProperty[]
32    @GET
33    @Path("properties/{property}")
34    @Produces(MediaType.TEXT_PLAIN)
35    public String queryProperty(@PathParam("property") String property) {
36        return System.getProperty(property);
37    }
38    // end::queryProperty[]
39
40    // tag::java[]
41    @GET
42    @Path("properties/java")
43    @Produces(MediaType.APPLICATION_JSON)
44    public JavaInfo java() {
45        JavaInfo javaInfo = new JavaInfo();
46        javaInfo.setVersion(System.getProperty("java.version"));
47        javaInfo.setVendor(System.getProperty("java.vendor"));
48        return javaInfo;
49    }
50    // end::java[]
51
52    // tag::note[]
53    @POST
54    @Path("note")
55    @Consumes(MediaType.TEXT_PLAIN)
56    @Produces(MediaType.APPLICATION_JSON)
57    public Response editNote(String text) {
58        System.setProperty("note", text);
59        return Response.ok().build();
60    }
61    // end::note[]
62
63}

SystemMetricsResource.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 v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.system;
14
15import java.lang.management.ManagementFactory;
16import java.lang.management.MemoryMXBean;
17import java.lang.management.OperatingSystemMXBean;
18
19import javax.enterprise.context.ApplicationScoped;
20import javax.ws.rs.GET;
21import javax.ws.rs.Path;
22import javax.ws.rs.Produces;
23import javax.ws.rs.core.MediaType;
24
25import io.openliberty.guides.graphql.models.SystemLoadData;
26import io.openliberty.guides.graphql.models.SystemMetrics;
27
28@ApplicationScoped
29@Path("metrics")
30public class SystemMetricsResource {
31
32    private static final OperatingSystemMXBean OS_MEAN =
33                             ManagementFactory.getOperatingSystemMXBean();
34
35    private static final MemoryMXBean MEM_BEAN = ManagementFactory.getMemoryMXBean();
36
37    // tag::systemMetrics[]
38    @GET
39    @Produces(MediaType.APPLICATION_JSON)
40    public SystemMetrics getSystemMetrics() {
41        SystemMetrics metrics = new SystemMetrics();
42        metrics.setProcessors(OS_MEAN.getAvailableProcessors());
43        metrics.setHeapSize(MEM_BEAN.getHeapMemoryUsage().getMax());
44        metrics.setNonHeapSize(MEM_BEAN.getNonHeapMemoryUsage().getMax());
45        return metrics;
46    }
47    // end::systemMetrics[]
48
49    // tag::systemLoad[]
50    @GET
51    @Path("/systemLoad")
52    @Produces(MediaType.APPLICATION_JSON)
53    public SystemLoadData getSystemLoad() {
54        SystemLoadData systemLoadData = new SystemLoadData();
55        systemLoadData.setLoadAverage(OS_MEAN.getSystemLoadAverage());
56        systemLoadData.setHeapUsed(MEM_BEAN.getHeapMemoryUsage().getUsed());
57        systemLoadData.setNonHeapUsed(MEM_BEAN.getNonHeapMemoryUsage().getUsed());
58        return systemLoadData;
59    }
60    // end::systemLoad[]
61}

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

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.

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

Enabling GraphQL

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

Replace the Maven project file.
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 server needs to be configured to support the GraphQL query language.

Replace the server configuration file.
graphql/src/main/liberty/config/server.xml

The mpGraphQL-1.0 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.

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>graphql</artifactId>
  9    <version>1.0-SNAPSHOT</version>
 10    <packaging>war</packaging>
 11
 12    <properties>
 13        <maven.compiler.source>1.8</maven.compiler.source>
 14        <maven.compiler.target>1.8</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.default.http.port>9082</liberty.var.default.http.port>
 19        <liberty.var.default.https.port>9445</liberty.var.default.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>8.0.0</version>
 28            <scope>provided</scope>
 29        </dependency>
 30        <dependency>
 31            <groupId>org.eclipse.microprofile</groupId>
 32            <artifactId>microprofile</artifactId>
 33            <version>4.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>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>1.1.0</version>
 53            <scope>provided</scope>
 54        </dependency>
 55        <!-- end::graphQLDependency[] -->
 56        <!-- For tests -->
 57        <dependency>
 58            <groupId>org.apache.httpcomponents</groupId>
 59            <artifactId>httpclient</artifactId>
 60            <version>4.5.13</version>
 61            <scope>test</scope>
 62        </dependency>
 63        <dependency>
 64            <groupId>org.junit.jupiter</groupId>
 65            <artifactId>junit-jupiter</artifactId>
 66            <version>5.7.2</version>
 67            <scope>test</scope>
 68        </dependency>
 69        <dependency>
 70            <groupId>org.apache.cxf</groupId>
 71            <artifactId>cxf-rt-rs-client</artifactId>
 72            <version>3.4.4</version>
 73            <scope>test</scope>
 74        </dependency>
 75        <dependency>
 76            <groupId>org.apache.cxf</groupId>
 77            <artifactId>cxf-rt-rs-extension-providers</artifactId>
 78            <version>3.4.4</version>
 79            <scope>test</scope>
 80        </dependency>
 81        <dependency>
 82            <groupId>org.glassfish</groupId>
 83            <artifactId>javax.json</artifactId>
 84            <version>1.1.4</version>
 85            <scope>test</scope>
 86        </dependency>
 87        <dependency>
 88            <groupId>org.eclipse</groupId>
 89            <artifactId>yasson</artifactId>
 90            <version>1.0.9</version>
 91            <scope>test</scope>
 92        </dependency>
 93    </dependencies>
 94
 95    <build>
 96        <finalName>${project.artifactId}</finalName>
 97        <plugins>
 98            <!-- Enable liberty-maven plugin -->
 99            <plugin>
100                <groupId>io.openliberty.tools</groupId>
101                <artifactId>liberty-maven-plugin</artifactId>
102                <version>3.5.1</version>
103                <configuration>
104                    <looseApplication>false</looseApplication>
105                </configuration>
106            </plugin>
107            <plugin>
108                <groupId>org.apache.maven.plugins</groupId>
109                <artifactId>maven-war-plugin</artifactId>
110                <version>3.3.2</version>
111            </plugin>
112            <plugin>
113                <groupId>org.apache.maven.plugins</groupId>
114                <artifactId>maven-surefire-plugin</artifactId>
115                <version>2.22.2</version>
116            </plugin>
117            <!-- Plugin to run functional tests -->
118            <plugin>
119                <groupId>org.apache.maven.plugins</groupId>
120                <artifactId>maven-failsafe-plugin</artifactId>
121                <version>2.22.2</version>
122                <configuration>
123                    <systemPropertyVariables>
124                        <http.port>${liberty.var.default.http.port}</http.port>
125                    </systemPropertyVariables>
126                </configuration>
127            </plugin>
128        </plugins>
129    </build>
130</project>

server.xml

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

Building and running the application

From the start directory, run the following commands:

mvn -pl models install
mvn clean 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.

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

docker build -t system:1.0-java8-SNAPSHOT --build-arg JAVA_VERSION=java8 system/.
docker build -t system:1.0-java11-SNAPSHOT --build-arg JAVA_VERSION=java11 system/.
docker build -t graphql:1.0-SNAPSHOT graphql/.

The --build-arg parameter is used to create two different system services. One uses Java 8, while the other uses Java 11. 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 8:

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

The output is similar to the following example:

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

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

mutation {
  editNote(
    hostname: "system-java8"
    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-java8") {
    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-java11 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-java8", "system-java11"]) {
    hostname
    loadData {
      heapUsed
      nonHeapUsed
      loadAverage
    }
  }
}

The response is similar to the following example:

{
  "data": {
    "systemLoad": [
      {
        "hostname": "system-java8",
        "loadData": {
          "heapUsed": 32432048,
          "nonHeapUsed": 85147084,
          "loadAverage": 0.36
        }
      },
      {
        "hostname": "system-java11",
        "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

Copied to clipboard
Copy code block
Copy file contents

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