JavaRush /Курсы /Модуль 3: Django /Практическое занятие по оптимизации GraphQL API

Практическое занятие по оптимизации GraphQL API

Модуль 3: Django
25 уровень , 9 лекция
Открыта

На протяжении последних лекций мы изучали, как работать с GraphQL в Django. Мы узнали, как создавать сложные вложенные запросы и эффективно управлять выборкой данных. Вы освоили оптимизацию запросов при помощи директив, фрагментов и DataLoader, устраняя проблему N+1 запросов. Мы реализовали пагинацию, сортировку и фильтрацию, чтобы предоставлять пользователям гибкость работы с данными. Также мы рассмотрели обработку ошибок и создание кастомных мутаций.

Всё это подготовило вас к сегодняшнему практическому занятию, где мы будем углубляться в оптимизацию и тестирование реальных сценариев.

Практическое задание

Мы продолжаем работу над нашим приложением — платформой для просмотра и управления статьями. Ваша задача — реорганизовать текущие запросы, внедрить оптимизации и добавить кастомные мутации. Весь ваш код должен быть максимально эффективным и готовым к работе в реальных условиях.

1. Оптимизация сложного запроса

Пусть у нас есть две модели: Article (статья) и Author (автор). Они связаны отношением ForeignKey.

# models.py

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Article(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()
    author = models.ForeignKey(Author, related_name='articles', on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

Часто пользователи хотят получать список статей вместе с информацией об их авторах. Давайте реализуем GraphQL-запрос для этого, но избегая проблемы N+1 запросов.

Запрос без оптимизации

Если бы мы оставили всё как есть, то запрос к каждому автору создавал бы N отдельных запросов к базе данных.

# schema.py

import graphene
from graphene_django.types import DjangoObjectType
from .models import Article, Author

class AuthorType(DjangoObjectType):
    class Meta:
        model = Author

class ArticleType(DjangoObjectType):
    class Meta:
        model = Article

class Query(graphene.ObjectType):
    all_articles = graphene.List(ArticleType)

    def resolve_all_articles(root, info):
        # ВНИМАНИЕ: Этот код генерирует N+1 запросов!
        return Article.objects.all()

Запрос в клиенте:

query {
  allArticles {
    title
    author {
      name
    }
  }
}

Оптимизация через select_related

Используем select_related(), чтобы заранее подтянуть данные авторов, уменьшив количество запросов к базе данных.

class Query(graphene.ObjectType):
    all_articles = graphene.List(ArticleType)

    def resolve_all_articles(root, info):
        # Используем select_related для оптимизации
        return Article.objects.select_related('author').all()

Теперь запросы к базе данных сведутся к одному, независимо от числа статей.

2. Пагинация

Добавим пагинацию, чтобы возвращать данные частями. Это особенно важно, если в базе тысячи записей.

from graphene_django.fields import DjangoConnectionField

class ArticleNode(DjangoObjectType):
    class Meta:
        model = Article
        interfaces = (graphene.relay.Node,)

class Query(graphene.ObjectType):
    all_articles = DjangoConnectionField(ArticleNode)

# Теперь запрос поддерживает пагинацию через Relay:

Пример запроса:

query {
  allArticles(first: 3) {
    edges {
      node {
        title
        author {
          name
        }
      }
    }
  }
}

3. Кастомная мутация для создания статьи

Реализуем кастомную мутацию для добавления новой статьи. Она должна валидировать входные данные (например, чтобы не допустить пустого названия) и возвращать информационное сообщение при успешном создании.

Реализация мутации

class CreateArticle(graphene.Mutation):
    class Arguments:
        title = graphene.String(required=True)
        description = graphene.String(required=True)
        author_id = graphene.ID(required=True)

    success = graphene.Boolean()
    article = graphene.Field(ArticleType)

    def mutate(self, info, title, description, author_id):
        if not title.strip():
            raise Exception("Title is required")

        author = Author.objects.get(pk=author_id)
        article = Article.objects.create(
            title=title,
            description=description,
            author=author
        )
        return CreateArticle(success=True, article=article)

class Mutation(graphene.ObjectType):
    create_article = CreateArticle.Field()

Пример запроса:

mutation {
  createArticle(title: "GraphQL Optimization", description: "This is about optimization", authorId: 1) {
    success
    article {
      title
      author {
        name
      }
    }
  }
}

4. Фильтрация и сортировка

Добавим возможность фильтровать статьи по автору и сортировать их по дате создания.

Используем библиотеку graphene-django-filter.

pip install django-filter
from graphene_django.filter import DjangoFilterConnectionField

class ArticleNode(DjangoObjectType):
    class Meta:
        model = Article
        interfaces = (graphene.relay.Node,)
        filter_fields = {
            'author__name': ['exact', 'icontains'],
            'title': ['exact', 'icontains'],
            'created_at': ['gte', 'lte']
        }

class Query(graphene.ObjectType):
    all_articles = DjangoFilterConnectionField(ArticleNode)

Пример запроса:

query {
  allArticles(author_Name_Icontains: "John", orderBy: "-created_at") {
    edges {
      node {
        title
        createdAt
      }
    }
  }
}

5. Интеграция с DataLoader

Чтобы избежать проблему N+1 не только на уровне связанных моделей, но и при сложных связях, используем DataLoader.

import dataloader

class ArticleLoader(dataloader.DataLoader):
    def batch_load_fn(self, keys):
        articles = Article.objects.filter(pk__in=keys)
        article_map = {article.pk: article for article in articles}
        return [article_map.get(key) for key in keys]

article_loader = ArticleLoader()

Используем его в резолверах:

def resolve_all_articles(root, info):
    return article_loader.load_many([article.pk for article in Article.objects.all()])

6. Анализ производительности

Используем django-debug-toolbar для профилирования запросов. Она покажет, сколько запросов выполняется, и поможет выявить места для оптимизации.

Установка и настройка:
pip install django-debug-toolbar

Добавьте в INSTALLED_APPS и настройте MIDDLEWARE.

Зайдите на любую страницу с GraphQL-запросами и посмотрите статистику по запросам.

Что ж, вам остаётся внедрить всё перечисленное выше в своё приложение, протестировать производительность запросов и попробовать разработать ещё более сложные запросы и мутации. Если вы освоите эти техники, у вас будет отличный аргумент на собеседовании или проекте: "А я знаю, как избавляться от N+1 запросов и оптимизировать GraphQL!"

1
Задача
Модуль 3: Django, 25 уровень, 9 лекция
Недоступна
Оптимизация с использованием DataLoader
Оптимизация с использованием DataLoader
1
Задача
Модуль 3: Django, 25 уровень, 9 лекция
Недоступна
Реализация и оптимизация мутации
Реализация и оптимизация мутации
3
Опрос
Работа с фильтрацией и сортировкой данных, 25 уровень, 9 лекция
Недоступен
Работа с фильтрацией и сортировкой данных
Работа с фильтрацией и сортировкой данных
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Oleg Evtushenko Уровень 90
5 октября 2025
💪 спасибо за подготовленный материал курса! за огромную проделанную работу!