Schema, types, queries, mutations, subscriptions, DataLoader
REST отдаёт фиксированные ресурсы по фиксированным URL. В GraphQL клиент сам описывает, какие поля ему нужны, и получает ровно их — одним запросом к единственному эндпоинту /graphql/. Так уходят две боли REST: over-fetching (пришло лишнее) и under-fetching (пришлось делать три запроса вместо одного).
💡 Правило: GraphQL оправдан, когда у вас много разных клиентов с разными потребностями в данных (мобайл, веб, партнёры) или сложные связанные сущности. Для простого CRUD-API REST/DRF проще и предсказуемее. GraphQL — это мощность ценой сложности.
| Аспект | REST | GraphQL |
|---|---|---|
| Эндпоинты | Много (/users/, /posts/) | Один (/graphql/) |
| Форма ответа | Задаёт сервер | Задаёт клиент |
| Over/under-fetching | Частая проблема | Решено по дизайну |
| Кэширование по HTTP | Простое (URL+метод) | Сложное (всё POST на один URL) |
| Версионирование | /v1/, /v2/ | Эволюция схемы (deprecate полей) |
| Загрузка сервера | Предсказуемая | Клиент может запросить дорогой запрос |
⚠️ Гибкость GraphQL — палка о двух концах: клиент может составить запрос, который породит тяжёлую нагрузку. Нужны ограничения глубины и сложности запросов (depth/complexity limiting), иначе это вектор DoS.
Современный выбор для Django — Strawberry (типобезопасный, на dataclass-подобном синтаксисе и аннотациях). Альтернатива — Graphene, более старая и многословная.
# schema.py
import strawberry
import strawberry_django
from typing import List
from .models import Post as PostModel, Author as AuthorModel
@strawberry_django.type(AuthorModel)
class Author:
id: int
name: str
posts: List['Post'] # связь разворачивается по запросу клиента
@strawberry_django.type(PostModel)
class Post:
id: int
title: str
content: str
author: Author
@strawberry.type
class Query:
posts: List[Post] = strawberry_django.field()
@strawberry.field
def post(self, id: int) -> Post | None:
return PostModel.objects.filter(pk=id).first()В GraphQL чтение идёт через Query, а любое изменение — через Mutation. Это явное разделение по контракту.
@strawberry.type
class Mutation:
@strawberry.mutation
def create_post(self, title: str, content: str, author_id: int) -> Post:
return PostModel.objects.create(
title=title, content=content, author_id=author_id,
)
schema = strawberry.Schema(query=Query, mutation=Mutation)# urls.py
from strawberry.django.views import GraphQLView
urlpatterns = [
path('graphql/', GraphQLView.as_view(schema=schema)),
]# Query: берём ровно нужные поля, включая связанного автора — один round-trip
query {
posts {
title
author { name }
}
}
# Mutation
mutation {
createPost(title: "Привет", content: "...", authorId: 1) {
id
title
}
}Клиент попросил title и author.name — content и остальное сервер не вернёт.
GraphQL провоцирует N+1 сильнее, чем REST. Запрос posts { author { name } } наивно выполнит 1 запрос за посты + по запросу на автора каждого поста. И вы не контролируете, какие связи запросит клиент.
Решение — паттерн DataLoader: он собирает все id за один «тик» выполнения и делает один батч-запрос вместо N.
from strawberry.dataloader import DataLoader
async def load_authors(keys: list[int]) -> list[Author]:
authors = {a.id: a async for a in AuthorModel.objects.filter(id__in=keys)}
return [authors.get(k) for k in keys] # порядок ответа = порядок keys!
author_loader = DataLoader(load_fn=load_authors)
@strawberry_django.type(PostModel)
class Post:
@strawberry.field
async def author(self) -> Author:
return await author_loader.load(self.author_id) # батчится автоматически⚠️ DataLoader обязателен для любого нетривиального GraphQL-API. Без него каждая связь — это N+1, и производительность деградирует пропорционально размеру выборки.
strawberry_djangoчастично решает это через автоматическую оптимизацию queryset, но для кастомных резолверов DataLoader нужен явно.
Один публичный эндпоинт, принимающий произвольные запросы, требует защиты:
| Угроза | Защита |
|---|---|
Глубоко вложенные запросы (a{b{c{d{...}}}}) | Лимит глубины (max_depth) |
| Дорогие запросы | Лимит сложности (complexity scoring) |
| Интроспекция схемы наружу | Отключить introspection в production |
| Перебор | Throttling/rate limiting |
| Утечка ошибок | Не отдавать трейсбеки клиенту |
from strawberry.extensions import QueryDepthLimiter
schema = strawberry.Schema(
query=Query, mutation=Mutation,
extensions=[QueryDepthLimiter(max_depth=10)],
)💡 GraphiQL (интерактивная песочница) удобен в разработке, но в production его и интроспекцию обычно выключают — чтобы не раскрывать всю схему API наружу.
Query (чтение), Mutation (изменение)GraphQLView в DjangoВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.
Далее: Оптимизация Производительности