Issue
Trying to migrate a Netflix DGS GraphQL and Neo4J project to now use Spring for GraphQL and Neo4J instead. Hit a roadblock when I wanted to avoid the N+1 problem.
Solution
Yes, there is an alternative to avoid the N+1 problem in Spring for GraphQL. It's the @BatchMapping annotation:
Suppose you have the following schema:
type Query {
artistas: [Artista]
}
type Artista {
id: ID
apellido: String
estilo: String
obras:[Obra]
}
type Obra{
artistaId: ID
titulo: String
imagen: String
}
And the following @QueryMapping:
@QueryMapping
Flux<Artista> artistas(){
return Flux.fromIterable(allArtistas);
}
Our Artista DTO contains a List of Obra we may sometimes want, so it can cause us the N+1 problem:
record Artista (Long id, String apellido, String estilo, List<Obra> obras){}
record Obra (Long artistaId, String titulo, String imagen){}
So if you add an additional mapping method annotated with @BatchMapping, you tell the GraphQL engine to fetch that data using a DataLoader under the hood and keeping it at hand for each DB roundtrip, for example.
@BatchMapping(typeName = "Artista")
Mono<Map<Artista, List<Obra>>> obras(List<Artista> artistas){
var artistasIds = artistas.stream()
.map(Artista::id)
.toList();
var todasLasObras = obtenerObras(artistasIds);
return todasLasObras.collectList()
.map(obras -> {
Map<Long, List<Obra>> obrasDeCadaArtistaId = obras.stream()
.collect(Collectors.groupingBy(Obra::artistaId));
return artistas.stream()
.collect(Collectors.toMap(
unArtista -> unArtista, //K, el Artista
unArtista -> obrasDeCadaArtistaId.get(Long.parseLong(unArtista.id().toString())))); //V, la lista de obras
});
}
private Flux<Obra> obtenerObras(List<Long> artistasIds) {
// ...your service-specific way of getting all the Obras from each artistaId...
}
If you throw some logs here and there you can check it only fetches the Obras once.
Hope it helps!
Answered By - Julio César Estravis
Answer Checked By - David Marino (JavaFixing Volunteer)