Skip to content

client_more_than_one_graphql_servers

etienne-sf edited this page Jun 22, 2024 · 13 revisions

Introduction

Starting with release 1.17, the generated code can attack as many GraphQL servers as you want. Previous releases could be configured to connect to only one GraphQL server.

This comes with little impact on the configuration. You'll find all the info on this page.

Spring Application

Generate the code

In order to be able to attack more several GraphQL servers, you must generate the code for each of them. So the plugin must be executed once for each GraphQL schema. The reasons for that are:

  • Some utilities classes are generated, with the same name, but a different content for each GraphQL schema.
  • The Spring name must be generated for each GraphQL server and GraphQL schema
    • Important notice: to distinguish the Spring beans of each schema, you MUST use the springBeanSuffix plugin parameter with a different value for each schema (see below for more information)

You can find a sample for that in the project:

Here is an extract of the pom.xml file (for Maven), that describes this:

...
	<build>
		<plugins>
			<plugin>
				<groupId>com.graphql-java-generator</groupId>
				<artifactId>graphql-maven-plugin</artifactId>
				<executions>
					<execution>
						<id>graphql-schema1</id>
						<goals>
							<goal>generateClientCode</goal>
						</goals>
						<configuration>

.. You can let the configuration for the first GraphQL schema unchanged

							<!-- But it's a good idea to have a specific prefix, still -->
							<springBeanSuffix>Schema1</springBeanSuffix>
						</configuration>
					</execution>
					<execution>
						<id>graphql-schema2</id>
						<goals>
							<goal>generateClientCode</goal>
						</goals>
						<configuration>
							<!-- You must define a specific target for the generated sources for at least all GraphQL schemas but one -->
							<targetSourceFolder>${basedir}/target/generated-sources/graphql-maven-plugin2</targetSourceFolder>
							<!-- You must define a suffix different for each GraphQL schemas -->
							<springBeanSuffix>Schema2</springBeanSuffix>

.. Then all plugin parameters, as usual, for this GraphQL schema

					</execution>
				</executions>
			</plugin>

...

		</plugins>
	</build>
...

TODO: add a maven sample

Define the URL for each GraphQL server

Of course, you have to provide the URL for each GraphQL server.

The property name for that is: graphql.endpointXxxx.url, where Xxxx is the value of the springBeanSuffix plugin parameter (as defined in your pom.xml or build.gradle).

Typically, you'll put these lines in your application.properties file:

graphql.endpointAllGraphQLCases.url = http://localhost:8180/my/updated/graphql/path
graphql.endpointForum.url = http://localhost:8182/graphql

...

Wire each GraphQL schema configuration in your Spring Context

It's essentially the same as with one unique GraphQL schema: you Spring Configuration class must scan:

  • Your current package (or any root package from which Spring will find your Spring beans)
  • The plugin runtime
  • Each target package for each GraphQL schema.

In the allGraphQLCases provided sample, you'll find the Main class:

@SuppressWarnings("deprecation")
@SpringBootApplication(scanBasePackageClasses = { Main.class, GraphQLConfiguration.class, MyQueryTypeExecutor.class,
		QueryExecutor.class })
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,
		DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
@EnableGraphQLRepositories({ "org.allGraphQLCases.demo.impl", "org.allGraphQLCases.subscription.graphqlrepository" })
public class Main implements CommandLineRunner {

...

}

What's noticeable here is:

  • [VERY IMPORTANT] The @SpringBootApplication annotation specifies these packages to scan. This must include the query executors that has been generated for each schema. In the above sample, there is:
    • Main's package (the application one)
    • GraphQLConfiguration's package (the runtime)
    • One QueryExecutor for each generated schema :
      • MyQueryTypeExecutor's package (the allGraphQLCases GraphQL schema)
      • QueryExecutor's package (the forum GraphQL schema)
    • No MutationExecutor or SubscriptionExecutor: only one class per generated schema
  • @EnableAutoConfiguration(exclude avoids to add Spring Data autoconfiguration stuff (Hibernate, JPA...)
  • The @EnableGraphQLRepositories allows to use the [GraphQL Repositories|client_graphql_repository] defined in these packages

Personalize the Spring bean (OAuth...)

The Spring bean that are specific to each GraphQL schema are configured in the xxx.util.SpringConfigurationXxx package, where Xxx is the springBeanSuffix defined in the pom.xml or in the build.gradle file.

You can override all these beans, for your needs: define a bean of the same and type, and mark it as @Primary. Your bean will replace the default provided one.

These beans are:

| String graphqlEndpointForumUrl | The URL for this GraphQL server | | WebClient webClientForum | The WebClient: you can define your own WebSocketClient with specific properties, if needed | | WebSocketClient webSocketClientForum | The WebSocketClient: you can define your own WebSocketClient with specific properties, if needed | | OAuthTokenExtractor oAuthTokenExtractorForum | (mostly internal: a tool that extracts the OAuth token to reuse it in WebSocket) | | QueryExecutor queryExecutorForum | The instance that will execute requests for this GraphQL schema | | GraphQLConfiguration graphQLConfigurationForum | (mostly internal to the plugin, subjet to changes) |

Manage OAuth for at least one of the GraphQL servers

To activate OAuth in a Spring app, you just have to provide a ServerOAuth2AuthorizedClientExchangeFilterFunction bean.

The issue here, is that the OAuth configuration will vary, depending on the GraphQL server. And some may be protected by OAuth, and others no.

If at least one of the GraphQL servers that you attack is protected by OAuth, you MUST provide a ServerOAuth2AuthorizedClientExchangeFilterFunction for each server:

  • Either with the OAuth configuration for this server
  • Or null if this server isn't protected by OAuth.

The name of this bean MUST be: serverOAuth2AuthorizedClientExchangeFilterFunctionXxxx, where Xxx is the springBeanSuffix defined in the pom.xml or in the build.gradle file.

For instance, in the allGraphQLCases provided sample, you'll find the beans definition in the Main class:

...
	@Bean
	ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunctionAllGraphQLCases(
			ReactiveClientRegistrationRepository clientRegistrations) {
		ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
				clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
		oauth.setDefaultClientRegistrationId("provider_test");
		return oauth;
	}

	@Bean
	ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunctionForum(
			ReactiveClientRegistrationRepository clientRegistrations) {
		return null;
	}
...

As you can see, the AllGraphQLCases server is protected by OAuth, and the Forum one isn't.

Using GraphQL Repositories with Spring against several GraphQL servers

In this case, two additional rules apply:

  • One GraphQL Repository may contain requests (queries, mutations and/or subscriptions) based on only one unique GraphQL schema
  • The QueryExecutor of this GraphQL schema must be provided in the queryExecutor parameter of the GraphQLRepository java annotation

Here is a sample:

@GraphQLRepository(queryExecutor = MyQueryTypeExecutor.class)
public interface PartialRequestGraphQLRepository extends PartialQueries {

... Your Request definitions go here

}

As MyQueryTypeExecutor is the query executor for the allGraphQLCases schema, all queries, mutations and/or subscriptions of this GraphQL Repositories must be based on this GraphQL schema. It's name this way, as the Query type's name in this GraphQL schema is MyQueryType.

The default name for a Query type is actually Query. In this case, the query executor is named QueryExecutor.

Non-Spring Application

With non-Spring application, as it's up to you to build the GraphQLConfiguration, or the query, mutation and subscription classes, you should almost no difference.

The main thing is to generate the code: the plugin must be executed once per GraphQL schema. And each execution must have its own target for the generated classes, as some utilities classes are generated, with the same name, but a different content for each GraphQL schema.

The sample for the Spring Application, here above, are correct also for non-Spring apps. The only difference is that the springBeanSuffix plugin parameter is useless for non-Spring application.

Clone this wiki locally