Search code examples
javaspringgraphql-javaspring-graphql

Is there a spring for graphql alternative for netflix's @DGSDataloader and DGSData


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!