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!"

3
Опитування
Робота з фільтрацією та сортуванням даних, рівень 25, лекція 9
Недоступний
Робота з фільтрацією та сортуванням даних
Робота з фільтрацією та сортуванням даних
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ