Home

Awesome

A tutorial for the GraphQL Maven plugin (server side)

This Tutorial describes how-to create a GraphQL server, with the graphql-maven-plugin and the graphql Gradle plugin.

The GraphQL plugin helps both on the server and on the client side. You'll find the tutorial here the client Maven tutorial and the client Gradle tutorial.

Schema first

This plugin allows a schema first approach.

This approach is the best approach for APIs: it allows to precisely control the Interface Contract. This contract is the heart of all connected systems.

This tutorial won't describe how to create a GraphQL schema. There are plenty of resources on the net for that, starting with the official GraphQL site.

The Forum GraphQL schema

This sample is based on the Forum schema, available here.

This schema contains:

This schema is stored in the /src/main/resources/ project folder for convenience.

It could be also be used in another folder, like /src/main/graphql/. In this case, the schema is not stored in the packaged jar (which is Ok), and you have to use the plugin schemaFileFolder parameter, to indicate where to find this schema.

The Maven pom.xml and Gradle build.gradle files

As a Maven or a Gradle plugin, you have to add the plugin in the build:

Let's first have a look at the Maven pom.xml file:


	<properties>
		<graphql-maven-plugin.version>2.4</graphql-maven-plugin.version>
	</properties>

	<build>
...
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
					<releasse>1.8</releasse>
				</configuration>
			</plugin>
			<plugin>
				<groupId>com.graphql-java-generator</groupId>
				<artifactId>graphql-maven-plugin</artifactId>
				<version>${graphql-maven-plugin.version}</version>
				<executions>
					<execution>
						<goals>
							<goal>generateServerCode</goal>
						</goals>
					</execution>
				</executions>
				<configuration>
					<!-- Let's configure the GraphQL Gradle Plugin: -->
					<!-- All available parameters are described here: -->
					<!-- https://graphql-maven-plugin-project.graphql-java-generator.com/graphql-maven-plugin/generateServerCode-mojo.html -->
					<packageName>org.forum.server.graphql</packageName>
					<scanBasePackages>org.forum.server.impl, org.forum.server.jpa</scanBasePackages>
					<customScalars>
						<customScalar>
							<graphQLTypeName>Date</graphQLTypeName>
							<javaType>java.util.Date</javaType>
							<graphQLScalarTypeStaticField>com.graphql_java_generator.customscalars.GraphQLScalarTypeDate.Date
							</graphQLScalarTypeStaticField>
						</customScalar>
					</customScalars>
					<!-- The parameters below change the 1.x default behavior. They are set to respect the behavior of the future 2.x versions -->
					<copyRuntimeSources>false</copyRuntimeSources>
					<generateBatchLoaderEnvironment>true</generateBatchLoaderEnvironment>
					<separateUtilityClasses>true</separateUtilityClasses>
					<skipGenerationIfSchemaHasNotChanged>true</skipGenerationIfSchemaHasNotChanged>
				</configuration>
			</plugin>
...
		</plugins>
	</build>

	<dependencies>
		<!-- Dependencies for GraphQL -->
		<dependency>
			<groupId>com.graphql-java-generator</groupId>
			<artifactId>graphql-java-server-runtime</artifactId>
			<version>${graphql-maven-plugin.version}</version>
		</dependency>

		<!-- Add of the graphiql interface, to test your GraphQL server -->
		<!-- It's available at http://localhost:8180/graphiql -->
		<dependency>
			<!-- com.graphql-java:graphiql-spring-boot-starter is deprecated. This 
				project has been moved to com.graphql-java-kickstart -->
			<groupId>com.graphql-java-kickstart</groupId>
			<artifactId>graphiql-spring-boot-starter</artifactId>
			<version>6.0.1</version>
			<scope>runtime</scope>
		</dependency>

	</dependencies>

Define once the plugin version in the build.properties file:

graphQLPluginVersion = 2.4

Then the Gradle build.gradle file:

plugins {
	id "com.graphql_java_generator.graphql-gradle-plugin" version "${graphQLPluginVersion}"
	id 'java'
}

repositories {
	mavenLocal()
	mavenCentral()
}

dependencies {
	// The graphql-java-runtime module agregates all dependencies for the generated code, including the plugin runtime
	// CAUTION: this version should be exactly the same as the graphql-gradle-plugin's version
	implementation 'com.graphql-java-generator:graphql-java-server-dependencies:${graphQLPluginVersion}'
	implementation 'com.github.dozermapper:dozer-core:6.5.0'
	implementation 'io.reactivex.rxjava2:rxjava:2.2.19'
	
	// The Spring Boot version should be the same as the Spring Boot version of the graphql-gradle-plugin
	implementation('org.springframework.boot:spring-boot-starter-data-jpa:2.4.4')
	
	runtimeOnly 'com.graphql-java-kickstart:graphiql-spring-boot-starter:6.0.1'
	runtimeOnly 'com.h2database:h2:2.1.210'
}

// The line below makes the GraphQL plugin be executed before Java compiles, so that all sources are generated on time
compileJava.dependsOn generateServerCode

// The line below adds the generated sources as a java source folder
sourceSets.main.java.srcDirs += '/build/generated/sources/graphqlGradlePlugin'

// Let's configure the GraphQL Gradle Plugin:
// All available parameters are described here: 
// https://graphql-maven-plugin-project.graphql-java-generator.com/graphql-maven-plugin/generateServerCode-mojo.html
generateServerCodeConf {
	packageName = 'org.forum.server.graphql'	
	scanBasePackages = 'org.forum.server.impl, org.forum.server.jpa'
	customScalars = [ [
			graphQLTypeName: "Date",
			javaType: "java.util.Date",
			graphQLScalarTypeStaticField: "com.graphql_java_generator.customscalars.GraphQLScalarTypeDate.Date"
	] ]

	// The parameters below change the 1.x default behavior. They are set to respect the behavior of the future 2.x versions
	copyRuntimeSources = false
	generateBatchLoaderEnvironment = true
	separateUtilityClasses = true
}

The compiler must be set to version 1.8 (or higher).

In this plugin declaration:

The generated source is added to the IDE sources, thanks to:

The graphql-java-server-dependencies dependency provides all the necessary dependencies, for the generated code. Of course, its version must be the same as the plugin's version.

The graphiql-spring-boot-starter adds a web GUI that allows to easily test your GraphQL server. It's very useful for testing. And it should of course be removed for production. This generate a web page that allow to execute GraphQL request. This page will be available on the /graphiql path of the server, once it is started.

A look at the generated code

Don't forget to execute (or re-execute) a full build when you change the plugin configuration, to renegerate the proper code:

If you're using eclipse (or other IDE), it may happen (especially with the Gradle plugin, that is less integrated), that you'll have to "Refresh the Gradle plugin" or the "Maven/Update Project...", to make your IDE properly see the generated code.

This will generate the client code in the packageName package (or in the com.generated.graphql if this parameter is not defined).

The code is generated in the :

Let's take a look at the generated code:

To sum up, you'll use:

That's all!

What is a Data Fetchers Delegate (that needs to be implemented)

The Data Fetchers are actually Spring beans, declared in the GraphQLDataFetchers generated class, like defined in the graphql-java doc.

These Data Fetchers are based on the Data Fetcher Delegates that you must provide:

In this sample based on the forum GraphQL schema, these Data Fetcher Delegates must be implemented:

A note on the database for this sample

This sample embeds an H2 in-memory database, that is field with sample data when the server starts. The H2 console is available at this URL: http://localhost:8180/h2-console. You'll have to enter this URL in the console to connect to the database: jdbc:h2:mem:testdb (all other parameters are correct)

This sample reuses what has been done for the graphql-maven-plugin-samples-Forum-server module of the graphql-maven-plugin. It contains these files, in the src/main/resources folder:

The application.properties file

The application.properties file is the standard Spring Boot configuration file. It allow to manage almost all the application parameters: server port, database parameters, tls... You'll lors of documentation about it on the net, starting with the Spring doc.

The application.properties for this sample is available here.

It's a very simple one, that contains these parameters:

How to access to the database (or any other kind of data source) ?

Use JPA annotations

The plugin contains a generateJPAAnnotation that adds JPA annotations into each data object created from the GraphQL schema. This allows to directly map you GraphQL entities into the database.

In this case, your code is minimal.

Use DTO

Using DTOs allows you to separate the GraphQL schema from your database schema. This allows you to manage differences between an objet schema (GraphQL) and a relational schema or any other kind of datasource.

You can see these two posts, that give more insight into this: How to use graphql with jpa if schema is different to database structure and Graphql Tools: Map entity type to graphql type.

It can be very interesting for complex schemas, for instance with Interfaces or Unions.

JPA or DTO in this tutorial

For this GraphQL schema, which is quite simple, the use of the generateJPAAnnotation plugin parameter is a perfect fit. You can see it in the forum sample embedded in the graphql maven plugin project.

In this tutorial, we'll separate the GraphQL objects from the JPA entities, to demonstrate the use of DTOs with the graphql plugin, as it should be used in real life complex projects.

Of course, as the backend is a relational database, we'll use JPA entities objects. These JPA entities are not generated by the plugin as, if you don't use the generateJPAAnnotation plugin parameter, it means that your relational schema differs from the GraphQL schema.

Another option is to start with generateJPAAnnotation plugin parameter set to true , which makes simpler code. Then, when it doesn't fit any more, switch to generateJPAAnnotation set to false and start using DTOs.

Creating the Data Fetchers Delegate

When developping a GraphQL server, based on the code generated by the graphql plugin, your "only" task is to implement the Data Fetchers Delegate. That is: create a Spring bean for each DataFetchersDelegateXxx interface that has been generated by the plugin.

You can find these interfaces in the <packageName>.util package, where:

For each of these DataFetchersDelegateXxx interfaces, you have to:

Please note that, when you change something in the Maven pom.xml or Gradle build.gradle file, you need to execute a mvn clean compile or a gradlew clean compileJava to regenerate the code.

Once you created all DataFetchersDelegate implementation, the server should be able to start.

For instance, you can create each DataFetchersDelegate implementation, with all methods returning null. This allows to check that the global code structure is ready.

Just execute the org.forum.server.graphql.util.GraphQLServerMain class to check that, where:

At this point, if it complains because it doesn't find a bean, check that:

Once all this is Ok, your code is wired to GraphQL. And it's up to you to implement what's specific to your use case.

Let's dot it, for this forum sample.

Update of the project configuration

For the next steps, we'll add these updates to the Maven pom.xml or Gradle build.gradle file:

Let's first add the JPA dependencies, the H2 database runtime and the Dozer dependency.

Here is for the pom Maven file:

	<dependencies>
...
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
			<version>2.2.6.RELEASE</version>
		</dependency>
		<dependency>
			<!-- Only here for the tests, to load the data in an in-memory database -->
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
			<version>1.4.199</version>
		</dependency>
		<dependency>
			<groupId>com.github.dozermapper</groupId>
			<artifactId>dozer-core</artifactId>
			<version>6.5.0</version>
		</dependency>
...
	</dependencies>

Then for the build.gradle Gradle file:

dependencies {
...
	
	implementation 'com.github.dozermapper:dozer-core:6.5.0'
	// The Spring Boot version should be the same as the Spring Boot version of the graphql-gradle-plugin
	implementation('org.springframework.boot:spring-boot-starter-data-jpa:2.2.6.RELEASE') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
    }
	runtime 'com.h2database:h2:1.4.199'
}

Then, we need to create the dozer Spring Bean. So we created a SpringConfig class, in the org.forum.server.impl package. This class:

import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import com.github.dozermapper.core.DozerBeanMapperBuilder;
import com.github.dozermapper.core.Mapper;

/**
 * This Spring Config class allows to add the specific Spring configuration part for your code.
 * 
 * @author etienne-sf
 */
@Configuration
@EnableJpaRepositories(basePackages = { "org.forum.server.jpa" })
@EntityScan(basePackages = { "org.forum.server.jpa" })
public class SpringConfig {

	@Bean
	Mapper mapper() {
		// The mapper can be configured to manage differences between the JPA Entities and the GraphQL objects/
		// In our case, there is no difference between them. So there is no need to define any mapping
		return DozerBeanMapperBuilder.buildDefault();
	}

}

As we added the org.forum.server.impl package in the scanBasePackages plugin parameter, Spring will discover bean (marked with @Component) and configuration files (marked with @Configuration). These configuration classes, like the one above, declare Spring beans with @Bean on methods. These methods returns Spring beans, which name are the method's name, and type is the method returning type.

You can then reuse these beans anywhere, by declaring them as a dependency in a class attribute like this:


import jakarta.annotation.Resource;
import com.github.dozermapper.core.Mapper;

@Component
public class MyUsefulClass {

	// mapper will be magically wired by Spring, when MyUsefulClass is instanciated
	@Resource
	private Mapper mapper;

...

}

The mapper attribute is then set when the MyBean is loaded, with the bean returned by the mapper() method of the above SpringConfig class.

You'll find lots of Spring documentation on the net, starting with the Spring reference doc

Creating the JPA Entities

We now need to create our JPA Entities.

As we expect differences between the database and the GraphQL schemas, we create a separate set of classes for the JPA Entities. These classes have been created in the org.forum.server.jpa package.

Let's have a look at the Topic Entity:

@Entity(name = "Topic")
public class TopicEntity {

	@Id
	@GeneratedValue
	UUID id;

	Date date;

	Boolean publiclyAvailable;

	Integer nbPosts;

	String title;

	String content;

	UUID boardId;

	UUID authorId;

	@Transient
	List<PostEntity> posts;
	
... All getters and setters  (you can use Lombok if you don't want to write them manually

}

All its fields maps the database schema, of course.

The @Entity annotation marks this class as a JPA Entity, and thus, it will be mapped to the database. We force its database name to be Topic , as the classname is different than the database table. We could have chosen to name this entity Topic, but it would be confusing to have exactly the same name as the GraphQL object.

The Id is marked as ID, and its value is generated by the framework.

Note the @Transient annotation, that makes sure that JPA won't load the objects that has a relation with this one: it's the GraphQL's job to optimize these accesses. So we don't want JPA framework to crawl through database relations.

Spring Discovery : the JPA Entities are discovered by Spring the package of the starting configuration ( packageName.util ) or one if its subpackage. In this tutorial, they are in another package. To manage this, there are two possibilities:

As we don't use the plugin generateJPAAnnotation plugin parameter, we created SpringConfig in the org.forum.server.impl and this package is given to the plugin with the scanBasePackages plugin parameter.

This SpringConfig allows us to add whatever Spring configuration we want. Here, like explained above, we added: the scan for JPA repsitories (@EnableJpaRepositories), the scan for JPA entities (@EntityScan) and the mapper bean.

Creating the Spring repositories

Spring data Repositories are magical: just declare an interface, and it will guess the query to execute!

If you use JPA, like in this sample, you should also take a look at the JPA Repositories.

In this tutorial, these repositories are stored in the org.forum.server.jpa.repositories package.

All repositories are standard Spring data repositories.

The only particular thing is the FindTopicRepository one. It adds a "complex" research capability, that is implemented in the FindTopicRepositoryImpl class.

The TopicRepository repository just implement the FindTopicRepository to benefit from this search capability.

Spring data and JPA repositories are very rich: you should really take a look at their document, if you haven't already, to see what capabilities they offer.

Once you've created the JPA Entities and the Spring Repositories, everything is ready for the actual Data Fetchers Delegate implementation.

Implementing the Data Fetchers Delegate

DTO versus JPA entities

As stated above, we'll separate the GraphQL objects from the JPA entities, to demonstrate the use of DTOs with the graphql plugin, as it should be used in real life complex projects.

As, in this current state, the database schema and the GraphQL schema same, we'll use Dozer to map the JPA entity. Dozer allow customization, if the two schemas slightly differs. If they become too different, you'll have to code the mapping yourself. In this case, you'll be happy to have this mapping layer between the database and the GraphQL schemas, as it allows you to hide the complexity to the GraphQL consumers.

Note about performance:

In this sample, we're using a standard relational database. So we let the Entity read the full object. And we fully map it into the GraphQL object. As said above, the graphql-java framework takes care of sending just the requested fields back to the consuming app.

Mapping from and to the JPA entity

In the above class, we declare the Dozer Mapper. In this tutorial, the mapping is straightforward. But Dozer can manage complex mapping, with either mapping files or Java configuration. You'll find all the information on the Dozer documentation.

Dozer is very easy to use, and requires no configuration at all for standard mapping. But the drawback for this, is that it's not very fast. The overhead is small, but other frameworks offer better performances. You can check the Baeldung test on mappers. The two fastest are:

As the Dozer map can't map a list of object, a utility method has been created in the Util class, that maps list of object. It is reused in the Data Fetcher Delegates

Let's start with the simplest query: boards

To check that everything is properly wired, we'll just implement the boards query.

It is very important to remember that the Data Fetchers should return only the Objects of the Data Fetcher , not the linked objects. For instance, a Board may contain several Topics . You should not query that attached Topics and if you return the Board with its Topics attached, these Topics will be ignored.

If the Topics for this Board are also queried, then the DataFetchersDelegateBoardImpl.topics() Data Fetcher will be called, to retrieve. We'll manage this in the later section.

As it's a query, and the query type is name Query in this GraphQL schema, this query is implemented in the DataFetchersDelegateQueryImpl class:

package org.forum.server.impl;

import java.util.List;

import jakarta.annotation.Resource;

import org.forum.server.graphql.Board;
import org.forum.server.graphql.Topic;
import org.forum.server.graphql.util.DataFetchersDelegateQuery;
import org.forum.server.jpa.BoardEntity;
import org.forum.server.jpa.repositories.BoardRepository;
import org.springframework.stereotype.Component;

import com.github.dozermapper.core.Mapper;

import graphql.schema.DataFetchingEnvironment;

@Component
public class DataFetchersDelegateQueryImpl implements DataFetchersDelegateQuery {

	@Resource
	private BoardRepository boardRepository;

	@Resource
	private Mapper mapper;

	@Resource
	private Util util;

	@Override
	public List<Board> boards(DataFetchingEnvironment dataFetchingEnvironment) {
		Iterable<BoardEntity> boards = boardRepository.findAll();
		return util.mapList(boards, BoardEntity.class, Board.class);
	}

	@Override
	public Integer nbBoards(DataFetchingEnvironment dataFetchingEnvironment) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<Topic> topics(DataFetchingEnvironment dataFetchingEnvironment, String boardName) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<Topic> findTopics(DataFetchingEnvironment dataFetchingEnvironment, String boardName,
			List<String> keyword) {
		// TODO Auto-generated method stub
		return null;
	}

}

This class is a Spring Bean, as it is marked by the @Component Spring annotation. It contains:

And you're done!

The graphql-java framework and the code generated by the plugin take care of handling the request, wiring it to this boards() method, get its result, and return it back to the caller.

Implementation of the Query DataFetchersDelegate

Note: it works the exact same way for Mutations. Subscriptions are different species: they are described latter, in this doc

Starts the server, by executing the org.forum.server.graphql.util.GraphQLServerMain class, available in the /target/generated-sources/graphql-maven-plugin folder (or /build/generated/sources/graphqlGradlePlugin for Gradle).

The output should finish by this message: Started GraphQLServerMain in xxx seconds

You can then access to graphiql on http://localhost:8180/graphiql, and execute the boards query by executing this request:

query {boards {id name}}

If the server didn't start, an error message is displayed. The most probable cause is that the Spring components are not properly configured. In this case, check that:

If all this fails, please raise an issue.

Implementation of the Board DataFetchersDelegate

As stated above, the DataFetchersDelegateQueryImpl.boards() that we implement should return only Board objects. It should not return any attached Topics : it's up to the GraphQL framework, to check if subobjects (like Topics here) are queried, and then call the appropriate Data Fetchers.

We now want to be able to query the boards and their Topics, like in this query:

query {boards {id name topics {id date title nbPosts }}}

To do that, the GraphQL framework will execute:

We also implement the unorderedReturnBatchLoader() method which will be explained in the next paragraph.

@Component
public class DataFetchersDelegateBoardImpl implements DataFetchersDelegateBoard {

	/** An internal utility to map lists of a type into list of another type */
	@Resource
	private Util util;

	@Resource
	BoardRepository boardRepository;
	@Resource
	TopicRepository topicRepository;

	@Override
	public List<Topic> topics(DataFetchingEnvironment dataFetchingEnvironment, Board origin, Date since) {
		List<TopicEntity> topics;

		// The query to execute depends on the since param: is it given?
		if (since == null) {
			topics = topicRepository.findByBoardId(origin.getId());
		} else {
			topics = topicRepository.findByBoardIdAndSince(origin.getId(), since);
		}

		return util.mapList(topics, TopicEntity.class, Topic.class);
	}

	@Override
	public List<Board> unorderedReturnBatchLoader(List<UUID> keys, BatchLoaderEnvironment env) {
		Iterable<BoardEntity> boards = boardRepository.findAllById(keys);
		return util.mapList(boards, BoardEntity.class, Board.class);
	}
}

The topics() method:

You can now start the GraphQL server, by executing the GraphQLServerMain class. Go to the http://localhost:8180/graphiql URL, then type the GraphQL request:

query {boards {id name topics {id date title nbPosts }}}

batchLoader and unorderedReturnBatchLoader methods: data retrieval for data loaders

The unorderedReturnBatchLoader(..) method is called by the data loader. It allows to retrieve a batch of data, in one step. This provides a great performance enhancement, as described in the plugin's wiki.

This wiki page details the differences between the batchLoader and unorderedReturnBatchLoader methods. Here is sumup:

Implementation of the other DataFetchersDelegate

We'll now implement the DataFetchersDelegateTopicImp, DataFetchersDelegatePostImpl and DataFetchersDelegateMemberImpl Data Fetcher Delegates.

With this step, we'll have the full query system implemented

The objective is to be able to also fetch the author of a Topic like in this GraphQL query:

query {boards {id name topics {id date title nbPosts author{id name email}}}}

The DataFetchersDelegateTopicImpl allows to navigate from a Topic to its author and its posts as defined in the GraphQL schema.

You'll see the first implementation of a fetcher that is based on a data loader. It basically 'just' declares the list of ids to retrieve. These ids will be retrieved by the batchLoader(..) or unorderedReturnBatchLoader(..) method, of the DataFetchersDelegateMember implementation.

	@Override
	public CompletableFuture<Member> author(DataFetchingEnvironment dataFetchingEnvironment,
			DataLoader<UUID, Member> dataLoader, Topic origin) {
		// TODO Store in cache the Topic (as it has already been read) to avoid the query below
		TopicEntity topic = topicRepository.findById(origin.getId()).get();

		return dataLoader.load(topic.getAuthorId());
	}

It allows to query only once the Member table, later, with all the member's ids.

Here is the full implementation for the DataFetchersDelegateTopicImpl Data Fetcher Delegate:

@Component
public class DataFetchersDelegateTopicImpl implements DataFetchersDelegateTopic {

	@Resource
	private Mapper mapper;
	/** An internal utility to map lists of a type into list of another type */
	@Resource
	private Util util;

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

	@Override
	public Member author(DataFetchingEnvironment dataFetchingEnvironment, Topic origin) {
		MemberEntity author = memberRepository.findAuthorOfTopic(origin.getId());
		return mapper.map(author, Member.class);
	}

	@Override
	public CompletableFuture<Member> author(DataFetchingEnvironment dataFetchingEnvironment,
			DataLoader<UUID, Member> dataLoader, Topic origin) {
		// TODO Store in cache the Topic (as it has already been read) to avoid the query below
		TopicEntity topic = topicRepository.findById(origin.getId()).get();

		return dataLoader.load(topic.getAuthorId());
	}

	@Override
	public List<Post> posts(DataFetchingEnvironment dataFetchingEnvironment, Topic origin, UUID memberId,
			String memberName, Date since) {
		List<PostEntity> posts;
		if (since == null) {
			// This should not happen, as since is mandatory
			throw new NullPointerException("since may not be null (its mandatory)");
		}

		// The memberId and memberName are Optional. The since param is mandatory.
		// So there are 4 combinations for the request.

		// since
		if (memberId == null && memberName == null) {
			posts = postRepository.findByTopicIdAndSince(origin.getId(), since);
		}
		// memberId, since
		else if (memberName == null) {
			posts = postRepository.findByTopicIdAndMemberIdAndSince(origin.getId(), memberId, since);
		}
		// memberName,since
		else if (memberId == null) {
			posts = postRepository.findByTopicIdAndMemberNameAndSince(origin.getId(), memberName, since);
		}
		// memberId, memberName, since
		else {
			posts = postRepository.findByTopicIdAndMemberIdAndMemberNameAndSince(origin.getId(), memberId, memberName,
					since);
		}

		return util.mapList(posts, PostEntity.class, Post.class);
	}

	@Override
	public List<Topic> unorderedReturnBatchLoader(List<UUID> keys, BatchLoaderEnvironment env) {
		Iterable<TopicEntity> topics = topicRepository.findAllById(keys);
		return util.mapList(topics, TopicEntity.class, Topic.class);
	}

}

Then the implementation for the DataFetchersDelegateMemberImpl

@Component
public class DataFetchersDelegateMemberImpl implements DataFetchersDelegateMember {

	/** An internal utility to map lists of a type into list of another type */
	@Resource
	private Util util;

	@Resource
	private MemberRepository memberRepository;

	@Override
	public List<Member> unorderedReturnBatchLoader(List<UUID> keys, BatchLoaderEnvironment env) {
		Iterable<MemberEntity> members = memberRepository.findAllById(keys);
		return util.mapList(members, MemberEntity.class, Member.class);
	}
}

You have to note here, that the mapper does a conversion between the type in the database and the Entity (which is a String) and the GraphQL member's type, which is a MemberType as defined in the GraphQL schema.

And the DataFetchersDelegatePostImpl implementation:

@Component
public class DataFetchersDelegatePostImpl implements DataFetchersDelegatePost {

	@Resource
	private Mapper mapper;
	/** An internal utility to map lists of a type into list of another type */
	@Resource
	private Util util;

	@Resource
	private MemberRepository memberRepository;
	@Resource
	private PostRepository postRepository;

	@Override
	public Member author(DataFetchingEnvironment dataFetchingEnvironment, Post origin) {
		MemberEntity author = memberRepository.findAuthorOfTopic(origin.getId());
		return mapper.map(author, Member.class);
	}

	@Override
	public CompletableFuture<Member> author(DataFetchingEnvironment dataFetchingEnvironment,
			DataLoader<UUID, Member> dataLoader, Post origin) {
		// TODO Store in cache the Post (as it has already been read) to avoid the query below
		PostEntity post = postRepository.findById(origin.getId()).get();

		return dataLoader.load(post.getAuthorId());
	}

	@Override
	public List<Post> unorderedReturnBatchLoader(List<UUID> keys, BatchLoaderEnvironment env) {
		Iterable<PostEntity> topics = postRepository.findAllById(keys);
		return util.mapList(topics, PostEntity.class, Post.class);
	}
}

With these implementation added, you can stop and restart the GraphQLServerMain server, and execute these queries, that also fetches authors and posts:

query {boards {id name topics {id date title nbPosts author{id name email}}}}
query {boards {id name topics {id date title nbPosts author{id name email} posts(since:"2019-01-01"){id title content date}}}}
query {boards {id name topics {id date title nbPosts author{id name email} posts(since:"2019-01-01"){id title content date author{id name email}}}}}

Implementing the mutations: DataFetchersDelegateSubscriptionImpl

To implement the mutations defined in the GraphQL schema, we just have to implement the method of the DataFetchersDelegateSubscription interface in a Spring Component, like the other Data Fetcher Delegates.

Again, most of the magic is done:

Here it goes:

@Component
public class DataFetchersDelegateMutationImpl implements DataFetchersDelegateMutation {

	@Resource
	private Mapper mapper;

	@Resource
	BoardRepository boardRepository;
	@Resource
	TopicRepository topicRepository;
	@Resource
	PostRepository postRepository;

	@Override
	public Board createBoard(DataFetchingEnvironment dataFetchingEnvironment, String name, Boolean publiclyAvailable) {
		BoardEntity board = new BoardEntity();
		board.setName(name);
		if (publiclyAvailable != null) {
			board.setPubliclyAvailable(publiclyAvailable);
		}
		boardRepository.save(board);
		return mapper.map(board, Board.class);
	}

	@Override
	public Topic createTopic(DataFetchingEnvironment dataFetchingEnvironment, TopicInput topicInput) {
		TopicEntity newTopic = new TopicEntity();
		newTopic.setBoardId(topicInput.getBoardId());
		newTopic.setAuthorId(topicInput.getInput().getAuthorId());
		newTopic.setPubliclyAvailable(topicInput.getInput().getPubliclyAvailable());
		newTopic.setDate(topicInput.getInput().getDate());
		newTopic.setTitle(topicInput.getInput().getTitle());
		newTopic.setContent(topicInput.getInput().getContent());
		newTopic.setNbPosts(0);
		topicRepository.save(newTopic);
		return mapper.map(newTopic, Topic.class);
	}

	@Override
	public Post createPost(DataFetchingEnvironment dataFetchingEnvironment, PostInput postParam) {
		PostEntity newPost = new PostEntity();
		newPost.setTopicId(postParam.getTopicId());
		newPost.setAuthorId(postParam.getInput().getAuthorId());
		newPost.setPubliclyAvailable(postParam.getInput().getPubliclyAvailable());
		newPost.setDate(postParam.getInput().getDate());
		newPost.setTitle(postParam.getInput().getTitle());
		newPost.setContent(postParam.getInput().getContent());
		postRepository.save(newPost);
		return mapper.map(newPost, Post.class);
	}

	@Override
	public List<Post> createPosts(DataFetchingEnvironment dataFetchingEnvironment, List<PostInput> spam) {
		// Actually, this mutation is for sample only. We don't want to implement it !
		// :)
		throw new RuntimeException("Spamming is forbidden");
	}
}

And hope, you can stop and restart the GraphQLServerMain server, and execute these queries, that create data into the database:

mutation {createBoard(name: "a new board's name",publiclyAvailable: true) {id, name, publiclyAvailable}}
mutation {
  createTopic(topic: {boardId: "00000000-0000-0000-0000-000000000001", input: {authorId: "00000000-0000-0000-0000-000000000001", date: "2020-05-31", publiclyAvailable: true, title: "a new title", content: "the new content"}}) {
    id
    title
    nbPosts
  }
}
mutation createPost {
  createPost(post: {topicId: "00000000-0000-0000-0000-000000000001", input: {authorId: "00000000-0000-0000-0000-000000000001", date: "2020-05-31", publiclyAvailable: true, title: " A new Post", content: " Some interesting news"}}) {
    id
    title
    content
  }
}

Implementing the Subscriptions: DataFetchersDelegateSubscriptionImpl

Like for queries and mutations, implementing the subscriptions defined in the GraphQL schema is "just" implementing all the Data Fetchers defined in the relevant Data Fetchers Delegate, that is implement all the methods of the DataFetchersDelegateSubscription interface in a Spring Component.

This being said, implementing a subscription is a little more complex, as you also have to provide the information flow that the customer apps will subscribed, with these subscriptions.

To do this, this tutorial uses ReactiveX, as the framework to manage this information flow.

This GraphQL schema defines one subscription: subscribeToNewPost

To implement it, we'll have to:

Let's go !

We first add the ReactiveX dependency in the Maven pom.xml or Gradle build.gradle file:

The Maven pom.xml file:

	<dependencies>
...
      <dependency>
        <groupId>io.reactivex.rxjava2</groupId>
        <artifactId>rxjava</artifactId>
        <version>2.2.19</version>
      </dependency>
...
	</dependencies>

Or the Gradle build.gradle file:

dependencies {

...

	implementation 'io.reactivex.rxjava2:rxjava:2.2.19'
}

Then, we create the ReactiveX Subject. To do that, we implement a PostPublisher class in the org.forum.server.impl package:

package org.forum.server.impl;

import org.forum.server.graphql.Post;
import org.reactivestreams.Publisher;
import org.springframework.stereotype.Component;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.subjects.PublishSubject;

@Component
public class PostPublisher {

	PublishSubject<Post> subject = PublishSubject.create();

	/**
	 * Let's emit this new {@link Post}
	 * 
	 * @param post
	 */
	void onNext(Post post) {
		subject.onNext(post);
	}

	/**
	 * Let's get a new publisher, for the GraphQL subscription that just occurred
	 * 
	 * @return
	 */
	Publisher<Post> getPublisher(String boardName) {
		return subject.toFlowable(BackpressureStrategy.BUFFER);
	}

}

Then, we update the mutation Data Fetcher createPost() so that it sends every new post to this ReactiveX Subject. Below are the changes in the DataFetchersDelegateMutationImpl implementation:

import io.reactivex.subjects.Subject;

@Component
public class DataFetchersDelegateMutationImpl implements DataFetchersDelegateMutation {

...
	/**
	 * This {@link Subject} will be notified for each Post creation. This is the basis for the <I>subscribeToNewPost</I>
	 * subscription
	 */
	@Resource
	PostPublisher postPublisher;

...

	@Override
	public Post createPost(DataFetchingEnvironment dataFetchingEnvironment, PostInput postParam) {
		PostEntity newPostEntity = new PostEntity();
		newPostEntity.setTopicId(postParam.getTopicId());
		newPostEntity.setAuthorId(postParam.getInput().getAuthorId());
		newPostEntity.setPubliclyAvailable(postParam.getInput().getPubliclyAvailable());
		newPostEntity.setDate(postParam.getInput().getDate());
		newPostEntity.setTitle(postParam.getInput().getTitle());
		newPostEntity.setContent(postParam.getInput().getContent());
		postRepository.save(newPostEntity);

		Post newPost = mapper.map(newPostEntity, Post.class);

		// Let's publish that new post, in case someone subscribed to the subscribeToNewPost GraphQL subscription
		postPublisher.onNext(newPost);

		return newPost;
	}
...
}

Now, the ReactiveX Subject receives each newly created post. We wire the GraphQL Subscription to the ReactiveX Subject, and we're done:

package org.forum.server.impl;

import jakarta.annotation.Resource;

import org.forum.server.graphql.Post;
import org.forum.server.graphql.util.DataFetchersDelegateSubscription;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import graphql.schema.DataFetchingEnvironment;
import io.reactivex.subjects.Subject;

@Component
public class DataFetchersDelegateSubscriptionImpl implements DataFetchersDelegateSubscription {

	/** The logger for this instance */
	protected Logger logger = LoggerFactory.getLogger(this.getClass());

	/**
	 * This {@link Subject} will be notified for each Post creation. This is the basis for the <I>subscribeToNewPost</I>
	 * subscription
	 */
	@Resource
	PostPublisher postPublisher;

	@Override
	public Publisher<Post> subscribeToNewPost(DataFetchingEnvironment dataFetchingEnvironment, String boardName) {
		logger.debug("Received a Subscription for {}", boardName);
		return postPublisher.getPublisher(boardName);
	}

}

The end...

Your GraphQL server is now fully implemented.

It's now up to you to update the GraphQL schema and add queries, mutations, subscriptions, objects, interfaces, unions....