Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User configured PreParsedDocumentProvider should be used for regular queries when APQ feature is enabled. #2128

Merged
merged 8 commits into from
Feb 24, 2025

Conversation

srinivasankavitha
Copy link
Contributor

@srinivasankavitha srinivasankavitha commented Feb 21, 2025

Pull Request type

  • Bugfix
  • Feature
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • Other (please describe):

Changes in this PR

Support for Automated Persisted Queries are implemented in graphql-java using a PreParsedDocumentProvider : https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/execution/preparsed/persisted/PersistedQuerySupport.java

The current setup allows us to configure only one PreParsedDocumentProvider. Therefore, if we enable APQ, we can only use a PreParsedDocumentProvider for APQ queries. Non-APQ or regular queries without a query hash will not be able to take advantage of a PreParsedDocumentProvider. Typically, users are able set up their own PreParsedDocumentProvider as a bean to speed up the request processing, especially critical for high scale applications.

This PR provides a wrapper to delegate to the appropriate PreParsedDocumentProvider based on whether the incoming query has an APQ hash or not. Also added some tests for the same.

Long term, we can work with spring or graphql-java to make this configurable.

Testing this feature:

  • Enable dgs.graphql.apq.enabled for the spring-graphql exampl app and start it
  • Run the following curl commands to test APQ functionality:
curl -X POST http://localhost:8080/graphql --header 'content-type: application/json' -d '{"query":"{__typename}","extensions":{"persistedQuery":{"version":1, "sha256Hash":"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"}}}'
curl -X POST http://localhost:8080/graphql --header 'content-type: application/json' -d '{"extensions":{"persistedQuery":{"version":1, "sha256Hash":"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"}}}'

Example of regular query:

curl -X POST http://localhost:8080/graphql --header 'content-type: application/json' -d '{"query":"{__typename}"}'

@srinivasankavitha srinivasankavitha changed the title Kavitha/apq enhancements User configured PreParsedDocumentProvider should be used for regular queries when APQ feature is enabled. Feb 21, 2025
@@ -40,7 +40,6 @@ open class DgsGraphQLMicrometerAutoConfiguration {
}

@Bean
@Order(PriorityOrdered.LOWEST_PRECEDENCE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this fix? The @Order is just bean creation order, so I want to make sure what the intent is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this is a previous commit, which I now removed because it doesn't do anything. The micrometer test started failing because the order in which the instrumentations run seems to have changed in my branch. If the DgsGraphQlJavaErrorInstrumentation runs before the MetricsInstrumentation, the tag value is set properly (which is in this branch). However, if it's the other way around, the metrics add the tag for gql.error before the DgsGraphQlJavaErrorInstrumentation runs and that's why it passes in master branch.

Basically need a way to ensure the MetricsInstrumentation runs last, but not sure what the best way to do that is, so looking into it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by specifying order on both instrumentation classes. That ensures the correct relative ordering.

@@ -387,7 +387,7 @@ class MicrometerServletSmokeTest {
.and("gql.operation.name", "anonymous")
.and("gql.query.complexity", "none")
.and("gql.query.sig.hash", "none")
.and("gql.errorDetail", "none")
.and("gql.errorDetail", "INVALID_ARGUMENT")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explained in response to earlier comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The micrometer test started failing because the order in which the instrumentations run seems to have changed in my branch. If the DgsGraphQlJavaErrorInstrumentation runs before the MetricsInstrumentation, the tag value is set properly (which is in this branch). However, if it's the other way around, the metrics add the tag for gql.error before the DgsGraphQlJavaErrorInstrumentation runs and that's why it passes in master branch.
The correct order should be DgsGraphQlJavaErrorInstrumentation first and then DgsMetricsInstrumentation so the metrics get the correct tag value as fixed in this PR.

Basically need a way to ensure the MetricsInstrumentation runs last, but not sure what the best way to do that is, so looking into it.

import java.util.concurrent.CompletableFuture
import java.util.function.Function

class DgsAPQPreParsedDocumentProvider(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be a more descriptive name, e.g. it should communicate that this is really a wrapper

graphQlBuilder
.preparsedDocumentProvider(preparsedDocumentProvider.get())
val apqEnabled = environment.getProperty("dgs.graphql.apq.enabled", Boolean::class.java, false)
// if apq is enabled use the APQPreparsedDocumentProvider instead
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment and if statement don't seem to match? The if statement checks for apq NOT being enabled?

Copy link
Contributor Author

@srinivasankavitha srinivasankavitha Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will clarify the comment - basically we don't want the code in the block to be used if apq is enabled.

}.returns(CompletableFuture.completedFuture(computeFunction(executionInput)))

dgsAPQPreParsedDocumentProvider.getDocumentAsync(executionInput, computeFunction)
assertThat(count == 1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertThat(count).isEqualTo(1) for better error reporting

@srinivasankavitha srinivasankavitha merged commit 0317b08 into master Feb 24, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants