GraphQL Maven Plugin (server mode)

This project is a maven plugin, which makes it easy to work in Java with graphQL in a schema first approach.

In server mode, the graphql-maven-plugin reads a graphqls schema, and generated the maximum of boilerplate code. That is, it generates:

Please note that the generated code uses dataloader to greatly improve the server’s performances. See https://github.com/graphql-java/java-dataloader.

Once all this is generated, you’ll have to implement the DataFetchersDelegate interfaces. The DataFetchersDelegate implementation is the only work that remains on your side. They are the link between the GraphQL schema and your data storage. See below for more details.

Depending on your use case, you can set the maven packaging to jar or war, in your pom. This changes the generated code. But your specific code is exactly the same. That is: you can change the packaging at any time, and it will still produce a ready-to-go product without any other modification from you.

Below you’ll find:

The parameters for the server are stored in the application.properties file.

You can have a look at this file, in the given server samples. It’s a standard spring boot configuration file, so you’ll find all the needed information on the net.

If you want to expose your Spring Boot app in https, take a look at the doc on the net. All parameters will go in the application.properties. For instance, Thomas Vitale provides a doc for that.

The important parameter is: server.port (for instance <server.port = 8180>), which determines the app port, when running as a spring boot app, that is, when it’s packaged as a jar.

The path depends on the way the GraphQL is run:

Create a new Maven Project, with this pom, for instance :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.3.RELEASE</version>
	</parent>

	<groupId>com.graphql-java</groupId>
	<artifactId>mytest-of-graphql-maven-plugin</artifactId>
	<version>0.1.0-SNAPSHOT</version>

	<build>
		<plugins>
			<plugin>
				<groupId>com.graphql-java</groupId>
				<artifactId>graphql-maven-plugin</artifactId>
				<version>1.12.3</version>
				<executions>
					<execution>
						<goals>
							<goal>graphql</goal>
						</goals>
					</execution>
				</executions>
				<configuration>
					<packageName>org.my.package</packageName>
					<mode>server</mode>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<executions>
					<execution>
						<goals>
							<goal>repackage</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
		<extensions>
			<!-- Adding these extensions prevents the error below, with JDK 9 and higher: -->
			<!-- NoSuchMethodError: 'java.lang.String javax.annotation.Resource.lookup()' -->
			<extension>
				<groupId>javax.annotation</groupId>
				<artifactId>javax.annotation-api</artifactId>
				<version>1.3.2</version>
			</extension>
			<extension>
				<groupId>javax.annotation</groupId>
				<artifactId>jsr250-api</artifactId>
				<version>1.0</version>
			</extension>
		</extensions>
	</build>

	<dependencies>
		<!-- Dependencies for tests -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-api</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- Dependencies for GraphQL -->
		<dependency>
			<groupId>com.graphql-java-kickstart</groupId>
			<artifactId>graphql-spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>com.graphql-java-kickstart</groupId>
			<artifactId>graphql-java-tools</artifactId>
		</dependency>
		<dependency>
			<!-- gives a GUI to test the GraphQL request on the generated server: http://localhost:8080/graphiql -->
			<groupId>com.graphql-java-kickstart</groupId>
			<artifactId>graphiql-spring-boot-starter</artifactId>
			<scope>runtime</scope>
		</dependency>

		<!-- Other dependencies -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.dbunit</groupId>
			<artifactId>dbunit</artifactId>
		</dependency>
	</dependencies>
</project>

Take care of the two parameters of the Maven Plugin that have been set there:

Then do a first build :

mvn clean install

The build will complain about the DataFetchersDelegate you need to define.

The short story is this one:

A longer story is this one:

The generated code can not be automatically adapted to all and every data model that exists, and even less all combinations between local and distant data that you may have on server side. So the generated code is only the basis for what’s most common to all implementations.

Then, it’s up to you to map the generated POJOs to your own data model.

In usual cases, this mapping is actually declaring the Spring Data Repositories, and call them from your implementation of the calling the DataFetchersDelegate interfaces, that have been generated by the graphql-java-generator plugin.

==> You can see such an example in the forum server sample. This sample is embedded into the plugin project, and is used as an integration test.

If the GraphQL schema is really different from the data model, then you may have to implement the relevant logic to fit your data model into the GraphQL model.

==> You can see such an example in the StarWars server sample. This sample is embedded into the plugin project, and is used as an integration test.

Write your implementation for the DataFetchersDelegate:

Basically, the plugin generates one DataFetchersDelegate interfaces for each object in the GraphQL schema, whether they are regular objects or query/mutation/subscription objects.

These DataFetchersDelegate interfaces contains one method for each Data Fetcher that must be implemented, and a batchLoader method used by the DataLoader:

The methods implemented by a DataFetchersDelegate are the actual DataFetchers. They can return (also see the sample below):

The DataFetchersDelegate implementation must be a Spring Bean (marked by the @Component spring framework annotation): Spring will magically discover them during the app or war startup: Spring is fantastic! :)

The only constraint you must respect is that these DataFetchersDelegate implementations are in the same package or a sub-package of the target package of the generated code. This package is:

So your DataFetchersDelegate implementation class will look like the sample below. Rather simple, isn’t it!

package com.graphql_java_generator.samples.forum.server.specific_code;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

import javax.annotation.Resource;

import org.dataloader.DataLoader;
import org.springframework.stereotype.Component;

import com.graphql_java_generator.samples.forum.server.GraphQLUtil;
import com.graphql_java_generator.samples.forum.server.Member;
import com.graphql_java_generator.samples.forum.server.Post;
import com.graphql_java_generator.samples.forum.server.Topic;
import com.graphql_java_generator.samples.forum.server.TopicDataFetchersDelegate;
import com.graphql_java_generator.samples.forum.server.jpa.MemberRepository;
import com.graphql_java_generator.samples.forum.server.jpa.PostRepository;
import com.graphql_java_generator.samples.forum.server.jpa.TopicRepository;

import graphql.schema.DataFetchingEnvironment;

@Component
public class DataFetchersDelegateTopicImpl implements DataFetchersDelegateTopic {

	@Resource
	MemberRepository memberRepository;
	@Resource
	PostRepository postRepository;
	@Resource
	TopicRepository topicRepository;

	@Resource
	GraphQLUtil graphQLUtil;

	@Override
	public CompletableFuture<Member> author(DataFetchingEnvironment dataFetchingEnvironment,
			DataLoader<UUID, Member> dataLoader, Topic source) {
		return dataLoader.load(source.getAuthorId());
	}

	@Override
	public List<Post> posts(DataFetchingEnvironment dataFetchingEnvironment, Topic source, String since) {
		if (since == null)
			return graphQLUtil.iterableToList(postRepository.findByTopicId(source.getId()));
		else
			return graphQLUtil.iterableToList(postRepository.findByTopicIdAndSince(source.getId(), since));
	}

	@Override
	public List<Topic> batchLoader(List<UUID> keys) {
		return topicRepository.findByIds(keys);
	}
}