Skip to content

server_migrate_1 x_to_2 x

etienne-sf edited this page Jun 16, 2023 · 22 revisions

Introduction

This page describe how to migrate a server implementation from a 1.x version to the last 2.x version.

If you're looking for a migration of the client implementation, please check the Client migration from 1.x to 2.x

The 2.x version is based on spring-graphql. Spring and spring-graphql are responsible for the whole transport part. The first versions are a mostly a port to use spring-graphql, with the lower impact possible on your exiting code.

Changes that may impact the existing implementations

A word on Spring dependencies

As the generated code is based on spring-graphql, spring dependencies are mandatory. spring-graphql doesn't choose between spring-mvc and spring-WebFlux. But the implementation that is done, based on spring-graphql, must choose between these two.

The generated code is based on Spring mvc.

As a consequence, in 2.x, the following dependencies are not needed by the generated code:

  • activation and spring-boot-starter: they are no more directly needd.
  • graphql-java-spring-mvc (the applications are now web flux apps)
  • spring-boot-starter-websocket (the Web Socket protocol is managed directly by spring-boot-starter-graphql)

Compatibility with Spring 3

The generated code is compatible with Spring 3.

If you're using the Gradle plugin you may have to check the configuration to build with Spring Boot 3

Replacement of import javax by import jakarta

If you use the generateJPAAnnotation plugin parameter with Spring 3, you must set the useJakartaEE9 plugin parameter to true.

Issues with the Gradle plugin

Gradle mixes dependencies between plugin executions: where everything is fine with Maven, the execution of the Gradle plugin may end with some ClassNotFoundException errors.

The workaround for this is to add the necessary dependency in a buildscript section, at the beginning of the build.gradle file, like this:

// The GraphQL plugin is compiled with Spring Boot 2.7.
// If you want to use it with Spring Boot 3.x, you have to update these dependencies, like this buildscript block:
buildscript {
    repositories {
    	// The GraphQL plugin depends on libraries that are available with maven
        mavenLocal()
        mavenCentral()
    }
	dependencies {
		classpath "org.springframework.boot:spring-boot-starter-graphql:${springBootVersion}"
    }
}


plugins {
	id 'java'
	// CAUTION !    The id of the plugin has changed since 2.0RC1.
	// It was 'com.graphql_java_generator.graphql-gradle-plugin'. It is now 'com.graphql-java-  generator.graphql-gradle-plugin'
	id 'com.graphql-java-generator.graphql-gradle-plugin' version "${graphQLPluginVersion}"
...
}

...

Please take care that, if you already have buildSrc folder, the spring-boot-starter-graphql must be added in the buildSrc folder you already have.

Impact on plugin parameters: change of some default values

It was planned to force some plugin parameters to specific values. But this would have to much impact in existing code. So only the default value changed: new users will have a better code generation. "Old" users can keep the behavior they had, to avoid any code modification.

The recommendation is to follow these default value. But you can force these parameters to their previous value, if it has too much impact on your code.

copyRuntimeSources (no defaults to false)

The default value was true. It is now false.

  • If copyRuntimeSources=true, the runtime is copied along with the generated code. The project configuration (pom.xml or build.gradle) must contain the com.graphql-java-generator:graphql-java-dependencies dependency, with the same version as the GraphQL plugin
  • If copyRuntimeSources=false, the runtime is NOT copied along with the generated code. The project configuration (pom.xml or build.gradle) must contain the com.graphql-java-generator:graphql-java-runtime dependency, with the same version as the GraphQL plugin

generateBatchLoaderEnvironment (now defaults to true)

It defines if the environment parameter is added to the batchLoader() methods of the generated DataFetchersDelegateXxxx interfaces. This environment parameter allows to get information from the GraphQL context.

Here is a sample of such a method:

@Override
public List<AllFieldCasesWithIdSubtype> batchLoader(List<UUID> keys, BatchLoaderEnvironment environment) {
	List<AllFieldCasesWithIdSubtype> list = new ArrayList<>(keys.size());
	for (UUID id : keys) {
		// Let's manage the uppercase parameter, that was associated with each key (and accessible through the environment)
		Boolean uppercase = (Boolean) environment.getKeyContexts().get(id);
		if (uppercase != null && uppercase) {
			item.setName(item.getName().toUpperCase());
		}
	}
	return list;
}

separateUtilityClasses (now defaults to true)

This parameter separates the utility classes generates by the plugin, from the generated classes that match the GraphQL schema (POJO...). This allows a better code structure, and may avoid some class name conflict. But changing this may have impact on existing code.

So, in 2.x versions:

  • Its default value changed from false to true
  • It is recommended to accept this default value, and adapt your code, as this separates the generated code that maps the GraphQL schema from the utility code that helps to start the server or execute the requests.
  • You can define its value to false, to avoid impact on your code.

Change your plugin from dependencies to runtime

A note about the copyRuntimeSources plugin parameter. If its previous value was unset, or set to true, then you must change your project dependency from dependencies to runtime. That is: change graphql-java-server-dependencies:pom (or graphql-java-client-dependencies:pom) to graphql-java-server-runtime:jar (or graphql-java-server-runtime:jar).

For instance, if you're using the plugin for a server, you must change:

		<dependency>
			<groupId>com.graphql-java-generator</groupId>
			<artifactId>graphql-java-server-dependencies</artifactId>
			<version>Plugin's version</version>
			<type>pom</type>
		</dependency>

to this:

		<dependency>
			<groupId>com.graphql-java-generator</groupId>
			<artifactId>graphql-java-server-runtime</artifactId>
			<version>Plugin's version</version>
		</dependency>

That is: change dependencies to runtime, and remove <type>pom</type>.

For Gradle, if you're using the plugin for a server, you must change:

    implementation "com.graphql-java-generator:graphql-java-server-dependencies:${project.pluginRuntimeVersion}" 

to this:

    implementation "com.graphql-java-generator:graphql-java-server-runtime:${project.pluginRuntimeVersion}" 

Task/Goal generatePojo

The default value for the copyRuntimeSources is false since 2.0. This implies to add the graphql-java-server-runtime:jar dependency to the project, that in turn implies other dependencies.

Thus, when using the Task/Goal generatePojo, it is recommended to set copyRuntimeSources to true. This avoid to add this runtime dependency.

Gradle plugin

Change of the id plugin

The id changed from com.graphql_java_generator.graphql-gradle-plugin to:

  • com.graphql-java-generator.graphql-gradle-plugin if you're building your project with Spring Boot 2 and Spring Framework 5
  • com.graphql-java-generator.graphql-gradle-plugin3 if you're building your project with Spring Boot 3 and Spring Framework 6

So the plugins section of the build.gradle is now:


plugins {
	// Starting from 2.0RC1, the top level domain changed from "com.graphql_java_generator" to "com.graphql-java-generator"
	id "com.graphql-java-generator.graphql-gradle-plugin" version "${graphQLPluginVersion}"
...
}

Add the generated resources

To run the project from your IDE, you must add the generated source folder and (which is new to 2.x version) the generated resources folder, like this:

sourceSets.main.java.srcDirs += '/build/generated/sources/graphqlGradlePlugin'
sourceSets.main.resources.srcDirs += '/build/generated/resources/graphqlGradlePlugin'

Configuration to build with Spring Boot 3

The GraphQL plugin is compiled with Spring Boot 2.7. When the build declared other plugins, and these plugins use Spring dependencies, there may be dependencies 'contentions'.

The errors you could encounter are typically:

  • ClassNotFound Error for the org.springframework.core.NestedIOException class
  • Issues when searching for the com.graphql-java-generator:graphql-maven-plugin-logic dependency

To solve this, you have to use the plugin3, like this:

plugins {
	// Starting from 2.0RC1, the top level domain changed from "com.graphql_java_generator" to "com.graphql-java-generator"
	id "com.graphql-java-generator.graphql-gradle-plugin3" version "${graphQLPluginVersion}"
...
}

dependencies {
	// Use the server runtime ... for the server side :) 
	implementation "com.graphql-java-generator:graphql-java-server-runtime:${graphQLRuntimeVersion}"

	// OR
	
	// Use the client runtime ... for the client side :) 
	implementation "com.graphql-java-generator:graphql-java-client-runtime:${graphQLRuntimeVersion}"
	
	...
}

See the Gradle change log for the version to use.

Local path of the server endpoint

In 1.x versions, the plugin allows to define the GraphQL endpoint's path with the graphql.url entry in the Spring configuration file (application.yml or application.properties) For servers only, in 2.x releases, this configuration is manager by spring. So the relevant configuration entry becomes spring.graphql.path in the Spring configuration file. For instance, for an application.properties file:

spring.graphql.path=/another-graphql-endpoint-path

In both 1.x and 2.x releases, the default value is /graphql

Exception management:

For Queries and Mutations

Like stated in the spring-graphql project, you can register a DataFetcherExceptionResolverAdapter that allows proper Exception management in your GraphQL server. There is a sample in the provided graphql-maven-plugin-samples-allGraphQLCases-server sample module. You just have to create a subclass of DataFetcherExceptionResolverAdapter, and override one of the resolveToSingleError or resolveToMultipleErrors methods.

Don't forget to add the @Component annotation, to register it.

For Subscription

It is undocumented, but the same mecanism exists for subscriptions exceptions. The class to override is SubscriptionExceptionResolverAdapter.

Support of WebSocket (mandatory for subscriptions)

The web socket transport is mandatory for subscription. To allow it, a line like this must be added in the application.properties (or application.yml ) file:

spring.graphql.websocket.path=/graphql

For spring-mvc, you also need add this dependency to your pom or gradle file:

		<!-- Mandatory to enable Web Sockets for Spring-mvc application (mandatory for subscription) -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

Other changes, for information

Schema files at runtime

The schema files are copied to the default spring-graphql folder (that is: the ./graphql folder in the classpath), as this is the default path wher spring-graphql looks for the GraphQL schema file.

Allow the RSocket protocol

RSocket is also supported (see the spring-graphql doc for more information on how to do this)

Internal wiring of the Data Fetchers

The main change between the original graphql-java implementation and the Spring logic is the way to wire the Data Fetchers to the GraphQL engine.

  • In 1.x branch (with graphql-java), this was done in the generated GraphQLWiring class. It used to wire the code for custom scalars and all GraphQL type, unions... Then, the GraphQLDataFetchers class would wire each DataFetcherDelegateXxxx method to the relevant non-scalar field.
  • spring-graphql uses a different logic: * The GraphQLWiring spring component still exists. But it only wires the custom scalar implementations. The custom scalars to use are declared in the plugin configuration, that in the pom.xml or build.gradle file, like in 1.x releases. * The Controllers: GraphQL web applications must declare a Controller for each GraphQL object, union... This is done by classes XxxxController that are generated by the plugin. These Controllers are responsible for managing each non-scalar field. * To keep compatibility with code written for 1.x releases of the plugin, the plugin generates these XxxxController classes. They are responsible for calling the DataFetchersDelegateXxxx components (including the BatchLoaders), and then reuse the code written for the 1.x release * It is planned to be able to override the default generated Controllers

Overriding of GraphQLDataFetchers replaced by overriding of the Spring controllers

The data fetchers are the component that query an object defined in the GraphQL schema as a type. It can be the query, mutation or subscription, or any other type defined in the GraphQL schema.

In 1.x, the way to override the default behaviour was to override the GraphQLDataFetchers spring component. But it is no more possible in 2.x versions.

With spring-graphql, the wiring between the GraphQL requests and the server code is done in the Spring Controllers. Since the 2.1 release, the Spring Controllers are defined in a default bean. It is possible to override them, by creating a bean of the same type and name, like this:

import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
import graphql.schema.DataFetchingEnvironment;

@Controller
@SchemaMapping(typeName = "MyQueryType")
public class MyQueryTypeController extends org.allGraphQLCases.server.MyQueryTypeController {

	@Override
	@SchemaMapping(field = "checkOverridedController")
	public String checkOverridedController(DataFetchingEnvironment dataFetchingEnvironment) {
		return "Welcome from the overridden controller";
	}

}

Manage exceptions DataFetcherExceptionResolver

It is possible to define a DataFetcherExceptionResolver. It is a custom Spring component that allows to manage the Exceptions thrown by the application, and define which ones must be sent as GraphQL Errors in the GraphQL response. The default behavior is to mask them into a generic server error.

You'll find a sample in the graphql-maven-plugin-samples-allGraphQLCases-server module of the 2.x branch. The org.allGraphQLCases.server.config.MyDataFetcherExceptionResolverAdapter checks if the thrown exception is an instance of GraphQlException, that is an Exception defined in this project. If it is the case, then it either maps to a new GraphQLError, or to the actual thrown exception (that implements GraphQLError).

GraphiQL

GraphiQL is available in spring-graphql. It is inactive by default.

It can be enabled with the spring.graphql.graphiql.enabled property, to define in the spring configuration file (application.properties or application.yml). It is then available at this path: /graphiql.

So there is no more need of the com.graphql-java-kickstart:graphiql-spring-boot-starter dependency.

Give access to the GraphQL schema

The spring.graphql.schema.printer.enabled property allows to expose the schema file in text format at /graphql/schema, to define in the spring configuration file (application.properties or application.yml).

Batch Loader / DataLoader

The Batch Loader is the capacity to use that is a very important optimization, on server side. You'll find the details in the java dataloader github page and in the graphql-java site.

The plugin doc for this subject is available in this wiki. Whether you use the 1.x and 2.x version of the plugin, your code implementation is the same.

What's change here is the way the plugin wires the Batch Loader method. With spring-graphql, the Batch Loader method are registered in a Spring BatchLoaderRegistry, within the constructor of the generated Controller, when needed. You'll find samples of that in the code generated for the graphql-maven-plugin-samples-Forum-server sample, for instance the TopicController.

How to personalize the 2.x version

Override the generated Spring Entity Controller

Starting with the 2.1, the GraphQL Spring Entity Controllers can be overridden. To do this:

  • Create a Spring Component (that is: create a dedicated class)
  • Mark it with the @Controller annotation
  • Check that it is in a folder that is scan by Spring.
    • You can use the scanBasePackages plugin parameter to add a new package to be scanned by Spring.
@Controller
@SchemaMapping(typeName = "MyQueryType")
public class MyQueryTypeController 
	// This controller can extend the generated controller, to override only one method
	// (that is: one GraphQL Data Fetcher)
	extends org.allGraphQLCases.server.MyQueryTypeController {

	@Override
	@SchemaMapping(field = "checkOverridedController")
	public String checkOverridedController(DataFetchingEnvironment dataFetchingEnvironment) {
		return "Welcome from the overridden controller";
	}

}
Clone this wiki locally