jerry.saravia
The purpose of this project is to highlight how unions and relay pagination types are not fully compatible in Spring Boot GraphQL. Additionally, I present a workaround to make them work together but hope that the Spring Boot GraphQL team will provide a better approach or guide this to a more final state.
- Spring 3.4.1
- Java 21
- Kotlin 1.9.25
# Install dependencies
./scripts/bootstrap.sh
# Compile/Build with tests, tests will fail by default
# Navigate to GraphQLUnionConfiguration and uncomment line 23 and include
# the @Bean annotation import to fix the unit test.
./gradlew build
# Run the service on the command line, or start it from Intellij if you want
./scripts/server.shRelay Pagination is supported in Spring Boot GraphQL so that in the following
schema the integers field will work correctly, but the integersUnion field will not.
See the schema file in this project for the schema.
You can start the service and see the empty response for yourself or
you can run the unit tests with ./gradlew clean build to see the failure. (sample query below)
type Query {
""" returns a list of integers from 1 to 1000 """
integers(first: Int, after: String): IntegerConnection!
""" Same as above except the connection object is wrapped in a union """
integersUnion(first: Int, after: String): IntegerResult!
}
union IntegerResult = IntegerConnection | NoIntegersFound
type IntegerConnection {
edges: [IntegerEdge]
pageInfo: PageInfo
}
type NoIntegersFound {
message: String!
}
type IntegerEdge {
node: Int
cursor: String
}
type PageInfo {
startCursor: String
endCursor: String
hasNextPage: Boolean
hasPreviousPage: Boolean
}query Integers {
integersUnion {
__typename
... on IntegerConnection {
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
node
}
}
... on NoIntegersFound {
message
}
}
}The resulting response is empty:
{
"data": {
"integersUnion": {
"__typename": "IntegerConnection",
"pageInfo": null,
"edges": null
}
}
}This is the expected result
{
"data": {
"integersUnion": {
"__typename": "IntegerConnection",
"pageInfo": {
"startCursor": "S19bImphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVNYXAiLHsic3RhcnQiOjF9XQ==",
"endCursor": "S19bImphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVNYXAiLHsic3RhcnQiOjEwfV0=",
"hasNextPage": true,
"hasPreviousPage": false
},
"edges": [
{
"node": 1
},
{
"node": 2
},
{
"node": 3
},
{
"node": 4
},
{
"node": 5
},
{
"node": 6
},
{
"node": 7
},
{
"node": 8
},
{
"node": 9
},
{
"node": 10
}
]
}
}
}Open this file and go to line 23 and add uncomment it so the bean gets created. (make sure to add the related import too at the top of the file)
Run the unit tests again and they'll pass.
Or you can run the project and attempt the query from above and notice that it works.
If you pass in a negative first parameter value you will see that the NoIntegersFound object is returned
instead of an IntegerConnection.
GraphQLUnionConfiguration will register the ConnectionUnderUnionTypeVisitor.
Read comments on the class for more details but in short it will:
- Look for union types being returned and in a decorator data fetcher.
- The decorator data fecher will execute the original data fetcher and if the return value
is a
SliceWrapperorWindowWrapperit will adapt the return value to aConnectionobject the same way thatConnectionFieldTypeVisitordoes. It also adds a type resolver for the union so it resolves the connection object to the Connection type.
Limitations:
- Does not support multiple connection objects under a union.
- Not sure if this is a good approach or if there is a better one.