I have the following schema structure (albeit with entity names changed):
type Artist {
name: String!
fans(first: Int, after: String, last: Int, before: String): FanConnection!
}
type ArtistConnection {
edges: [ArtistEdge!]!
pageInfo: PageInfo!
}
type ArtistEdge {
cursor: String!
node: Artist!
}
type FanConnection {
edges: [FanEdge!]!
pageInfo: PageInfo!
}
type FanEdge {
cursor: String!
node: Fan!
}
type Fan {
name: String!
}
type PageInfo {
hasPreviousPage: Boolean!
hasNextPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
artists(first: Int, after: String, last: Int, before: String): ArtistConnection!
}
My goal is to batch resolve Fan entities when listing artists and their fans. To do this I had been using Spring Data's Window as a return value from data fetchers (controller methods) which automatically is mapped to a GraphQL connection by Spring GraphQL. I am referencing the ScrollSubrange argument provided automatically by Spring GraphQl to my controller methods, therefore, I am unable to use the @BatchMapping annotation; this means I have to register batch data loaders manually, and given all these batch data loaders' values are 'Window.class', I have to register then using forName rather than forTypePair - which then means that I end up not being able to resolve Java type issues where the data loader is expecting Mono<Map<Object, Object>>> but my data loader is returning Mono<Map<Artist, Window<Fan>>>>.
What is the correct way to handle batch loading Fan entities for all Artist entities in the result of this query:
query {
artists {
name
fans(first: 10) {
name
}
}
}
I can't seem to find specific batch mapping + paging examples, or documentation anywhere.
Here's what I had so far:
Registration of the data loader:
FanController(final FanService fanService, final BatchLoaderRegistry batchLoaderRegistry) {
this.fanService = fanService;
batchLoaderRegistry
.forName("artistFansDataLoader")
.registerMappedBatchLoader((final Set<Artist> artists, final BatchLoaderEnvironment environment) -> {
final GraphQLContext graphQLContext = Optional.ofNullable(environment.getContext())
.map(GraphQLContext.class::cast)
.orElseThrow(() -> new InternalError("Failed to get GraphQL Context"));
final Identity identity = Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
.map(Authentication::getPrincipal)
.map(Identity.class::cast)
.orElseThrow(() -> new InternalError("Failed to get authentication principal"));
final ScrollSubrange scrollSubrange = graphQLContext.get("scrollSubrange");
return (Mono<Map<Object, Object>>) this.fansByArtists(artists, identity, scrollSubrange);
});
}
data fetcher / controller method:
@SchemaMapping(typeName = "Artist", field = "fans")
public CompletableFuture<Window<Fan>> artistFans(
final GraphQLContext context,
final ScrollSubrange scrollSubrange,
@Qualifier("artistFansDataLoader") final DataLoader<Artist, Window<Fan>> loader,
final Artist artist
) {
return loader.load(artist);
}
and finally, the data loader:
private Mono<Map<Artist, Window<Fan>>> fansByArtists(
final Set<Artist> artists,
final Identity identity,
final ScrollSubrange scrollSubrange
) {
final Map<Artist, Window> result = artists.parallelStream()
.collect(Collectors.toMap(
Function.identity(),
artist -> this.fanService.list(
new ListFansRequest(
identity.accountId(),
Collections.singleton(artist.id()),
scrollSubrange
)
)
));
return Mono.just(result);
}
This is the type error I'm seeing for the return statement in the registration of the batch data loader:
Inconvertible types; cannot cast 'Mono<Map<Artist,Window>>' to 'Mono<Map<Object,Object>>'
1 Answer 1
I have managed to get this working by providing generic arguments to the forName method of the BatchLoaderRegistry:
batchLoaderRegistry
.<Artist, Window<Fan>>forName("artistFansLoader")
.registerMappedBatchLoader(this::fansByArtistIds);
Now, I am able to properly type the data loader method without issue or raw type warnings.