spring-projects/spring-graphql

Replace samples

rstoyanchev opened this issue · 10 comments

The current samples in the repository depend on the Boot starter which is about to move to the Spring Boot repository, which also means that the samples need to move too. The plan is to turn them into tests spring-graphql and/or spring-graphql-test.

In addition we'll also create a getting started guide and a more advanced external sample.

Example scenarios to consider:

This is probably part of this issue, but the current link to samples in the documentation is broken. The link here https://docs.spring.io/spring-graphql/docs/current/reference/html/#samples points to https://github.com/spring-projects/spring-graphql/tree/main/samples (broken)

Thanks, @eduanb. This was addressed in 0baceda already.

Thanks, @eduanb. This was addressed in 0baceda already.

Hello,

The link is actually corrected in the 1.1.3 version. It would be nice if it is also corrected in the 1.0.4 doc.

Etienne

Is there still a chance to get this documented Provide guidance on how to set up multiple GraphQL endpoints #439?

Yes, it's the plan, however it hasn't been done. In the mean time, start with the Boot auto configuration, which shows how to set up a single endpoint, and go from there. If you want to make a sample repository somewhere, and you get stuck, we can have a look and help you.

Thanks @rstoyanchev for the reply and guidance. I actually made this work so I created a small project to showcase how I did it https://github.com/codesnippe/multiple-graphql-endpoints-demo. If you guys have time to have a look at it and give some feedback to see if this fine, or if they are any 'nice-to-know' or 'nice-to-have' things that could be done as well, I would really appreciate it. Thanks!

Hi. I've just finished my own attepmt to implement multiple graphql endpoints and now found this topic. I've notices that example does not provide ability to setup different packages to process external and internal schemas by different ways.

For example:
for external consumers i dont'want to fetch not processed books (e.g. checked for restricted content), but internal consumers should recive it. So in common case there is need different controllers for external/internal schemas, so in my example (Java) I've added PackagedAnnotatedControllerConfigurer, that overrides "detectHandlerMethods" and adds filters for loaded beans. But this way forced me to put this class in "org.springframework.graphql.data.method.annotation.support" package (beacuse there is several package-private fields/methods, that i could'nt use)

public class PackagedAnnotatedControllerConfigurer extends AnnotatedControllerConfigurer {
	private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";

	private String activePackage = "";

	public void setActivePackage(String activePackage) {
		this.activePackage = activePackage;
	}

	@Override
	protected Set<DataFetcherMappingInfo> detectHandlerMethods() {
		Set<DataFetcherMappingInfo> results = new LinkedHashSet<>();
		ApplicationContext context = obtainApplicationContext();
		for (String beanName : context.getBeanNamesForType(Object.class)) {
			if (beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				continue;
			}
			Class<?> beanType = null;
			try {
				beanType = context.getType(beanName);
			}
			catch (Throwable ex) {
				// An unresolvable bean type, probably from a lazy bean - let's ignore it.
				if (this.logger.isTraceEnabled()) {
					this.logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
				}
			}
			if (beanType == null || !AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || !isPackageMatched(beanType)) {
				continue;
			}
			Class<?> beanClass = context.getType(beanName);
			findHandlerMethods(beanName, beanClass).forEach((info) -> addHandlerMethod(info, results));
		}

		return results;
	}

	/**
	 * Actually added only this check. All of code is copy from source class
	 */
	private boolean isPackageMatched(Class<?> beanType) {
		return StringUtils.isEmpty(activePackage) || activePackage.contains(beanType.getPackageName());
	}

	private Collection<DataFetcherMappingInfo> findHandlerMethods(Object handler, @Nullable Class<?> handlerClass) {
		if (handlerClass == null) {
			return Collections.emptyList();
		}

		Class<?> userClass = ClassUtils.getUserClass(handlerClass);
		Map<Method, DataFetcherMappingInfo> map = MethodIntrospector.selectMethods(
				userClass, (Method method) -> getMappingInfo(method, handler, userClass));

		return map.values();
	}

	private void addHandlerMethod(DataFetcherMappingInfo info, Set<DataFetcherMappingInfo> results) {
		HandlerMethod handlerMethod = getHandlerMethod(info);
		DataFetcherMappingInfo existing = results.stream().filter((o) -> o.equals(info)).findFirst().orElse(null);
		if (existing != null && !getHandlerMethod(existing).equals(handlerMethod)) {
			throw new IllegalStateException(
					"Ambiguous mapping. Cannot map '" + handlerMethod.getBean() + "' method \n" +
							handlerMethod + "\n" + ": There is already '" +
							getHandlerMethod(existing).getBean() + "' bean method\n" + existing + " mapped.");
		}
		results.add(info);
		((AnnotatedControllerExceptionResolver)this.getExceptionResolver()).registerController(handlerMethod.getBeanType());
	}
}

and then add wiringConfigurer customization

	wiringConfigurers.orderedStream().forEach(customizer -> {
			if (customizer instanceof PackagedAnnotatedControllerConfigurer packaged) {
				packaged.setActivePackage(activePackage);
			}
			builder.configureRuntimeWiring(customizer);
		});

I suggest to partially change code of AnnotatedControllerConfigurer to build more clean way to implement ability of different packages for different endpoints

Best regards

@codesnippe I've added a link to your sample at spring-graphql-examples.

We now have a number of samples either hosted or listed at spring-graphql-examples. For further samples, please create an issue at that repository.