Guide d'Optimisation des Performances

Ce guide présente les meilleures pratiques pour optimiser les performances de vos APIs GraphQL générées avec GraphQL AutoGen.


Optimisation des Requêtes N+1

Le problème des requêtes N+1

Les requêtes N+1 sont l'un des problèmes de performance les plus courants dans les APIs GraphQL. Elles se produisent lorsque vous chargez une liste d'objets, puis exécutez une requête séparée pour chaque objet afin de récupérer une relation associée.

Exemple de requête GraphQL problématique
query {
  allOrders {    # 1 requête pour charger toutes les commandes
    id
    total
    customer {   # N requêtes pour charger le client de chaque commande
      id
      name
    }
  }
}

Solution: DataLoaders automatiques

GraphQL AutoGen génère automatiquement des DataLoaders pour vos relations entre entités. Voici comment les configurer de manière optimale :

Activation des DataLoaders
@Entity
@GraphQLType
public class Order {
    @Id
    private Long id;

    private BigDecimal total;

    @ManyToOne
    @GraphQLField
    @GraphQLDataLoader // Active le DataLoader pour cette relation
    private Customer customer;

    @OneToMany(mappedBy = "order")
    @GraphQLField
    @GraphQLDataLoader(batchSize = 100) // Configure la taille du batch
    private List items;

    // Getters et setters
}

DataLoaders personnalisés

Pour des cas plus complexes, vous pouvez implémenter vos propres DataLoaders :

DataLoader personnalisé
@Component
@GraphQLDataLoaderRegistration
public class CustomerDataLoader implements BatchLoader {

    private final CustomerRepository customerRepository;

    public CustomerDataLoader(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    @Override
    public CompletionStage> load(List customerIds) {
        // Optimisation : une seule requête pour tous les IDs
        return CompletableFuture.supplyAsync(() -> {
            List customers = customerRepository.findAllByIdIn(customerIds);

            // Préserver l'ordre des résultats selon les IDs demandés
            Map customerMap = customers.stream()
                .collect(Collectors.toMap(Customer::getId, Function.identity()));

            return customerIds.stream()
                .map(id -> customerMap.getOrDefault(id, null))
                .collect(Collectors.toList());
        });
    }
}

Pagination Efficace

La pagination est essentielle pour gérer efficacement les grandes collections de données :

Pagination de type Cursor (Relay)

Implémentation de pagination Relay
@GraphQLQuery(name = "products")
public Connection getProducts(
        @GraphQLArgument(name = "first", defaultValue = "10") int first,
        @GraphQLArgument(name = "after") String after,
        @GraphQLArgument(name = "filter") ProductFilter filter) {

    // Conversion du cursor en offset
    int offset = 0;
    if (after != null) {
        offset = ConnectionCursor.fromCursor(after).getOffset() + 1;
    }

    // Utilisation de Spring Data pour la pagination
    Pageable pageable = PageRequest.of(offset / first, first);
    Page productPage;

    if (filter != null) {
        productPage = productRepository.findAllWithFilter(filter, pageable);
    } else {
        productPage = productRepository.findAll(pageable);
    }

    // Conversion en format Connection de Relay
    return new SimpleConnection<>(productPage.getContent(), pageable,
            productPage.hasNext(), productPage.getTotalElements());
}

Mise en Cache

Mise en cache des résultats

GraphQL AutoGen s'intègre facilement avec Spring Cache pour améliorer les performances :

Configuration du cache
@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(
            new ConcurrentMapCache("products"),
            new ConcurrentMapCache("categories")
        ));
        return cacheManager;
    }
}

@Service
public class ProductService {

    private final ProductRepository productRepository;

    @Cacheable("products")
    public Product getProductById(Long id) {
        return productRepository.findById(id).orElse(null);
    }

    @Cacheable("products")
    public List getProductsByCategory(Long categoryId) {
        return productRepository.findByCategoryId(categoryId);
    }
}

Mise en cache du schéma

Activez la mise en cache du schéma pour améliorer les temps de démarrage :

application.properties
graphql.autogen.cache-schema=true
graphql.autogen.schema-cache-max-age=3600

Optimisation des Filtres et Tris

Implémentez des filtres et tris efficaces pour vos API GraphQL :

Filtres avancés
@GraphQLType(name = "ProductFilter")
public class ProductFilter {
    private String nameContains;
    private BigDecimal minPrice;
    private BigDecimal maxPrice;
    private List categoryIds;

    // Getters et setters
}

@Repository
public interface ProductRepository extends JpaRepository {

    @Query("SELECT p FROM Product p WHERE " +
           "(:#{#filter.nameContains} IS NULL OR LOWER(p.name) LIKE LOWER(CONCAT('%', :#{#filter.nameContains}, '%'))) AND " +
           "(:#{#filter.minPrice} IS NULL OR p.price >= :#{#filter.minPrice}) AND " +
           "(:#{#filter.maxPrice} IS NULL OR p.price <= :#{#filter.maxPrice}) AND " +
           "(:#{#filter.categoryIds} IS NULL OR p.category.id IN :#{#filter.categoryIds})")
    Page findAllWithFilter(@Param("filter") ProductFilter filter, Pageable pageable);
}

Conseil de performance

Utilisez des requêtes SQL natives ou des Specification de JPA pour les filtres complexes afin d'optimiser les requêtes en base de données.

Surveillance et Profilage

Mettez en place une surveillance pour identifier les goulots d'étranglement :

Instrumentation GraphQL

Instrumentation pour la surveillance des performances
@Configuration
public class GraphQLInstrumentationConfig {

    @Bean
    public Instrumentation requestLoggingInstrumentation() {
        return new RequestLoggingInstrumentation(options -> options
            .includeTiming(true)
            .includeQuery(true)
            .maxQueryLength(10000)
            .includeVariables(true));
    }

    @Bean
    public Instrumentation tracingInstrumentation() {
        return new TracingInstrumentation(TracingInstrumentation.Options.newOptions()
            .includeTrivialDataFetchers(false));
    }
}

Si vous rencontrez des conflits de types dans votre schéma GraphQL, consultez notre guide de résolution des conflits de types.