Протягом останніх лекцій ми вивчали, як працювати з 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!"
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ