На протяжении последних лекций мы изучали, как работать с 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!"
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ