Вступление
Веб-API - это движки, на которых сегодня работает большинство наших приложений. На протяжении многих лет REST был доминирующей архитектурой для API, но в этой статье мы рассмотрим GraphQL .
С помощью REST API вы обычно создаете URL-адреса для каждого доступного объекта данных. Допустим, мы создаем REST API для фильмов - у нас будут URL-адреса для самих фильмов, актеров, наград, режиссеров, продюсеров ... это уже становится громоздким! Это может означать множество запросов на один пакет связанных данных. Представьте, что вы пользуетесь мобильным телефоном с низким энергопотреблением и медленным интернет-соединением. Эта ситуация не идеальна.
GraphQL - это не архитектура API, как REST, это язык, который позволяет нам гораздо проще обмениваться связанными данными. Мы будем использовать его для разработки API для фильмов. После этого мы рассмотрим, как библиотека Graphene позволяет нам создавать API-интерфейсы на Python, создавая API-интерфейс фильма с помощью Django .
Что такое GraphQL
Первоначально созданный Facebook, но теперь разработанный в рамках GraphQL Foundation , GraphQL - это язык запросов и среда выполнения сервера, которая позволяет нам извлекать данные и манипулировать ими.
Мы используем строго типизированную систему GraphQL для определения данных, которые должны быть доступны для API. Затем мы создаем схему для API - набор разрешенных запросов для получения и изменения данных.
Разработка схемы фильма
Создание наших типов
Типы описывают тип данных, доступных в API. Уже предоставлены примитивные типы, которые мы можем использовать, но мы также можем определить наши собственные пользовательские типы.
Рассмотрим следующие типы для актеров и фильмов:
type Actor {
id: ID!
name: String!
}
type Movie {
id: ID!
title: String!
actors: [Actor]
year: Int!
}
Тип ID
сообщает нам, что поле является уникальным идентификатором для
этого типа данных. Если ID
не является строкой, для работы типа
требуется способ сериализации в строку!
Примечание . Восклицательный знак означает, что это поле обязательно для заполнения .
Вы также можете заметить, что в Movie
мы используем как примитивные
типы, такие как String
и Int
так и наш пользовательский тип Actor
Если мы хотим, чтобы поле содержало список типов, мы заключаем его в
квадратные скобки - [Actor]
.
Создание запросов
В запросе указывается, какие данные можно получить и что требуется для их получения:
type Query {
actor(id: ID!): Actor
movie(id: ID!): Movie
actors: [Actor]
movies: [Movie]
}
Этот Query
позволяет нам получить данные об Actor
и Movie
, указав
их ID
, или мы можем получить их список без фильтрации.
Создание мутаций
Мутация описывает, какие операции можно выполнять для изменения данных на сервере.
Мутации основаны на двух вещах:
- Входные данные - специальные типы используются только в качестве аргументов при мутации, когда мы хотим передать весь объект, а не отдельные поля.
- Полезные нагрузки - обычные типы, но по соглашению мы используем их в качестве выходных данных для мутации, чтобы мы могли легко расширять их по мере развития API.
Первое, что мы делаем, это создаем типы ввода:
input ActorInput {
id: ID
name: String!
}
input MovieInput {
id: ID
title: String
actors: [ActorInput]
year: Int
}
Затем мы создаем типы полезной нагрузки:
type ActorPayload {
ok: Boolean
actor: Actor
}
type MoviePayload {
ok: Boolean
movie: Movie
}
Обратите внимание на ok
Типы полезной нагрузки обычно включают
метаданные, такие как поле состояния или ошибки.
Тип Mutation
объединяет все вместе:
type Mutation {
createActor(input: ActorInput) : ActorPayload
createMovie(input: MovieInput) : MoviePayload
updateActor(id: ID!, input: ActorInput) : ActorPayload
updateMovie(id: ID!, input: MovieInput) : MoviePayload
}
createActor
нужен ActorInput
, который требует имени актера.
Для мутатора updateActor
ID
обновляемого актера, а также обновленная
информация.
То же самое и с мутаторами из фильмов.
Примечание . Хотя ActorPayload
и MoviePayload
не являются
необходимыми для успешной мутации, для API рекомендуется предоставлять
обратную связь при обработке действия.
Определение схемы
Наконец, мы сопоставляем созданные запросы и мутации со схемой:
schema {
query: Query
mutation: Mutation
}
Использование библиотеки графена
GraphQL не зависит от платформы, можно создать сервер GraphQL с различными языками программирования (Java, PHP, Go), фреймворками (Node.js, Symfony, Rails) или такими платформами, как Apollo.
С Graphene нам не нужно использовать синтаксис GraphQL для создания схемы, мы используем только Python! Эта библиотека с открытым исходным кодом также интегрирована с Django, поэтому мы можем создавать схемы, ссылаясь на модели нашего приложения.
Настройка приложения
Виртуальные среды
Лучшей практикой считается создание виртуальных сред для проектов
Django. Начиная с Python 3.6, venv
был включен для создания
виртуальных сред и управления ими.
Используя терминал, войдите в свое рабочее пространство и создайте следующую папку:
$ mkdir django_graphql_movies
$ cd django_graphql_movies/
Теперь создайте виртуальную среду:
$ python3 -m venv env
Вы должны увидеть новую env
в своем каталоге. Нам нужно активировать
нашу виртуальную среду, чтобы при установке пакетов Python они были
доступны только для этого проекта, а не для всей системы:
$ . env/bin/activate
Примечание . Чтобы выйти из виртуальной среды и использовать обычную
оболочку, введите deactivate
. Вы должны сделать это в конце урока.
Установка и настройка Django и Graphene
Находясь в нашей виртуальной среде, мы используем pip
для установки
Django и библиотеки Graphene:
$ pip install Django
$ pip install graphene_django
Затем мы создаем наш проект Django:
$ django-admin.py startproject django_graphql_movies .
Проект Django может состоять из множества приложений. Приложения - это повторно используемые компоненты в рамках проекта, и лучше всего создавать наш проект с их помощью. Создадим приложение для наших фильмов:
$ cd django_graphql_movies/
$ django-admin.py startapp movies
Прежде чем мы будем работать над нашим приложением или запускать его, мы синхронизируем наши базы данных:
# First return to the project's directory
$ cd ..
# And then run the migrate command
$ python manage.py migrate
Создание модели
Модели Django описывают структуру базы данных нашего проекта. Каждая модель представляет собой класс Python, который обычно сопоставляется с таблицей базы данных. Свойства класса сопоставляются со столбцами базы данных.
Введите следующий код в django_graphql_movies/movies/models.py
:
from django.db import models
class Actor(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Meta:
ordering = ('name',)
class Movie(models.Model):
title = models.CharField(max_length=100)
actors = models.ManyToManyField(Actor)
year = models.IntegerField()
def __str__(self):
return self.title
class Meta:
ordering = ('title',)
Как и в случае со схемой GraphQL, Actor
имеет имя, тогда как Movie
имеет заголовок, отношение «многие ко многим» с актерами и год.
Идентификаторы автоматически генерируются для нас Django.
Теперь мы можем зарегистрировать наше приложение для фильмов в проекте.
Перейдите в django_graphql_movies/settings.py
и измените
INSTALLED_APPS
на следующее:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_graphql_movies.movies',
]
Обязательно перенесите свою базу данных, чтобы синхронизировать ее с изменениями нашего кода:
$ python manage.py makemigrations
$ python manage.py migrate
Загрузка тестовых данных
После создания нашего API мы захотим иметь возможность выполнять
запросы, чтобы проверить, работает ли он. Давайте загрузим некоторые
данные в нашу базу данных, сохраним следующий JSON как movies.json
в
корневом каталоге вашего проекта:
[
{
"model": "movies.actor",
"pk": 1,
"fields": {
"name": "Michael B. Jordan"
}
},
{
"model": "movies.actor",
"pk": 2,
"fields": {
"name": "Sylvester Stallone"
}
},
{
"model": "movies.movie",
"pk": 1,
"fields": {
"title": "Creed",
"actors": [1, 2],
"year": "2015"
}
}
]
И выполните следующую команду, чтобы загрузить тестовые данные:
$ python manage.py loaddata movies.json
В терминале вы должны увидеть следующий вывод:
Installed 3 object(s) from 1 fixture(s)
Создание нашей схемы с графеном
Выполнение запросов
В папке приложения с фильмами создайте новый schema.py
и давайте
определим наши типы GraphQL:
import graphene
from graphene_django.types import DjangoObjectType, ObjectType
from django_graphql_movies.movies.models import Actor, Movie
# Create a GraphQL type for the actor model
class ActorType(DjangoObjectType):
class Meta:
model = Actor
# Create a GraphQL type for the movie model
class MovieType(DjangoObjectType):
class Meta:
model = Movie
С помощью Graphene, чтобы создать тип GraphQL, мы просто указываем, какая модель Django имеет свойства, которые нам нужны в API.
В том же файле добавьте следующий код для создания типа Query
# Create a Query type
class Query(ObjectType):
actor = graphene.Field(ActorType, id=graphene.Int())
movie = graphene.Field(MovieType, id=graphene.Int())
actors = graphene.List(ActorType)
movies= graphene.List(MovieType)
def resolve_actor(self, info, **kwargs):
id = kwargs.get('id')
if id is not None:
return Actor.objects.get(pk=id)
return None
def resolve_movie(self, info, **kwargs):
id = kwargs.get('id')
if id is not None:
return Movie.objects.get(pk=id)
return None
def resolve_actors(self, info, **kwargs):
return Actor.objects.all()
def resolve_movies(self, info, **kwargs):
return Movie.objects.all()
Каждое свойство Query
соответствует запросу GraphQL:
-
actor
иmovie
возвращают одно значениеActorType
иMovieType
соответственно, и для обоих требуется целочисленный идентификатор. -
actors
иmovies
возвращают список своих соответствующих типов.
Четыре метода, которые мы создали в классе Query, называются преобразователями . Решатели связывают запросы в схеме с фактическими действиями, выполняемыми базой данных. Как обычно в Django, мы взаимодействуем с нашей базой данных через модели.
Рассмотрим функцию resolve_actor
Мы получаем идентификатор из
параметров запроса и возвращаем актера из нашей базы данных с этим
идентификатором в качестве его первичного ключа. Функция
resolve_actors
просто получает всех участников в базе данных и
возвращает их в виде списка.
Создание мутаций
При разработке схемы мы сначала создали специальные типы ввода для наших
мутаций. Сделаем то же самое с Graphene, добавим это в schema.py
:
# Create Input Object Types
class ActorInput(graphene.InputObjectType):
id = graphene.ID()
name = graphene.String()
class MovieInput(graphene.InputObjectType):
id = graphene.ID()
title = graphene.String()
actors = graphene.List(ActorInput)
year = graphene.Int()
Это простые классы, которые определяют, какие поля можно использовать для изменения данных в API.
Создание мутаций требует немного больше работы, чем создание запросов. Добавим мутации для актеров:
# Create mutations for actors
class CreateActor(graphene.Mutation):
class Arguments:
input = ActorInput(required=True)
ok = graphene.Boolean()
actor = graphene.Field(ActorType)
@staticmethod
def mutate(root, info, input=None):
ok = True
actor_instance = Actor(name=input.name)
actor_instance.save()
return CreateActor(ok=ok, actor=actor_instance)
class UpdateActor(graphene.Mutation):
class Arguments:
id = graphene.Int(required=True)
input = ActorInput(required=True)
ok = graphene.Boolean()
actor = graphene.Field(ActorType)
@staticmethod
def mutate(root, info, id, input=None):
ok = False
actor_instance = Actor.objects.get(pk=id)
if actor_instance:
ok = True
actor_instance.name = input.name
actor_instance.save()
return UpdateActor(ok=ok, actor=actor_instance)
return UpdateActor(ok=ok, actor=None)
Вспомните сигнатуру createActor
когда мы разрабатывали нашу схему:
createActor(input: ActorInput) : ActorPayload
- Имя нашего класса соответствует имени запроса GraphQL.
- Внутренние
Arguments
соответствуют входным аргументам мутатора. - Свойства
ok
иactor
ActorPayload
.
Главное, что нужно знать при написании mutation
- это то, что вы
сохраняете данные в модели Django:
- Мы берем имя из входного объекта и создаем новый объект
Actor
- Мы вызываем
save
чтобы обновить нашу базу данных, и возвращаем полезные данные пользователю.
Класс UpdateActor
имеет аналогичную настройку с дополнительной логикой
для извлечения обновляемого актора и изменения его свойств перед
сохранением.
Теперь добавим мутацию для фильмов:
# Create mutations for movies
class CreateMovie(graphene.Mutation):
class Arguments:
input = MovieInput(required=True)
ok = graphene.Boolean()
movie = graphene.Field(MovieType)
@staticmethod
def mutate(root, info, input=None):
ok = True
actors = []
for actor_input in input.actors:
actor = Actor.objects.get(pk=actor_input.id)
if actor is None:
return CreateMovie(ok=False, movie=None)
actors.append(actor)
movie_instance = Movie(
title=input.title,
year=input.year
)
movie_instance.save()
movie_instance.actors.set(actors)
return CreateMovie(ok=ok, movie=movie_instance)
class UpdateMovie(graphene.Mutation):
class Arguments:
id = graphene.Int(required=True)
input = MovieInput(required=True)
ok = graphene.Boolean()
movie = graphene.Field(MovieType)
@staticmethod
def mutate(root, info, id, input=None):
ok = False
movie_instance = Movie.objects.get(pk=id)
if movie_instance:
ok = True
actors = []
for actor_input in input.actors:
actor = Actor.objects.get(pk=actor_input.id)
if actor is None:
return UpdateMovie(ok=False, movie=None)
actors.append(actor)
movie_instance.title=input.title
movie_instance.year=input.year
movie_instance.save()
movie_instance.actors.set(actors)
return UpdateMovie(ok=ok, movie=movie_instance)
return UpdateMovie(ok=ok, movie=None)
Поскольку фильмы ссылаются на актеров, мы должны получить данные об
актерах из базы данных перед сохранением. Цикл for
сначала проверяет,
что субъекты, предоставленные пользователем, действительно находятся в
базе данных, если нет, он возвращается без сохранения каких-либо данных.
При работе с отношениями «многие ко многим» в Django мы можем сохранять связанные данные только после сохранения нашего объекта .
Вот почему мы сохраняем наш фильм с помощью movie_instance.save()
перед тем, как movie_instance.actors.set(actors)
.
Чтобы завершить наши мутации, мы создаем Тип Мутации:
class Mutation(graphene.ObjectType):
create_actor = CreateActor.Field()
update_actor = UpdateActor.Field()
create_movie = CreateMovie.Field()
update_movie = UpdateMovie.Field()
Создание схемы
Как и раньше, когда мы разрабатывали нашу схему, мы сопоставляем запросы
и мутации с API нашего приложения. Добавьте это в конец schema.py
:
schema = graphene.Schema(query=Query, mutation=Mutation)
Регистрация схемы в проекте
Чтобы наш API работал, нам нужно сделать схему доступной для всего проекта.
Создайте новый файл schema.py
django_graphql_movies/
и добавьте
следующее:
import graphene
import django_graphql_movies.movies.schema
class Query(django_graphql_movies.movies.schema.Query, graphene.ObjectType):
# This class will inherit from multiple Queries
# as we begin to add more apps to our project
pass
class Mutation(django_graphql_movies.movies.schema.Mutation, graphene.ObjectType):
# This class will inherit from multiple Queries
# as we begin to add more apps to our project
pass
schema = graphene.Schema(query=Query, mutation=Mutation)
Отсюда мы можем зарегистрировать графен и указать ему использовать нашу схему.
Откройте django_graphql_movies/settings.py
и добавьте
'graphene_django',
в качестве первого элемента в INSTALLED_APPS
.
В том же файле добавьте следующий код на пару новых строк ниже
INSTALLED_APPS
:
GRAPHENE = {
'SCHEMA': 'django_graphql_movies.schema.schema'
}
Доступ к API GraphQL осуществляется через одну конечную точку /graphql
. Нам нужно зарегистрировать этот маршрут или, скорее, просмотр в
Django.
Откройте django_graphql_movies/urls.py
и измените содержимое файла на:
from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView
from django_graphql_movies.schema import schema
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql/', GraphQLView.as_view(graphiql=True)),
]
Тестирование нашего API
Чтобы протестировать наш API, запустим проект, а затем перейдем к конечной точке GraphQL. В терминале типа:
$ python manage.py runserver
Как только ваш сервер будет запущен, http://127.0.0.1:8000/graphql/
.
Вы столкнетесь с GraphiQL -
встроенной IDE для выполнения ваших запросов!
Написание запросов
Для нашего первого запроса давайте получим всех актеров в нашей базе данных. На левой верхней панели введите следующее:
query getActors {
actors {
id
name
}
}
Это формат запроса в GraphQL. Мы начинаем с query
, за которым следует
необязательное имя запроса. Рекомендуется давать запросам имена,
поскольку это помогает при ведении журнала и отладке. GraphQL позволяет
нам указывать и те поля, которые нам нужны - мы выбрали id
и name
.
Несмотря на то, что у нас есть только один фильм в наших тестовых
данных, давайте попробуем movie
и обнаружим еще одну замечательную
функцию GraphQL:
query getMovie {
movie(id: 1) {
id
title
actors {
id
name
}
}
}
Для movie
требуется идентификатор, поэтому мы указываем его в скобках.
Интересный момент связан с полем actors
В нашей модели Django мы
включили actors
в наш Movie
и указали между ними отношение «многие
ко многим». Это позволяет нам получить все свойства типа Actor
,
связанные с данными фильма.
Этот графоподобный обход данных - основная причина, по которой GraphQL считается мощной и захватывающей технологией!
Написание мутаций
Мутации следуют тому же стилю, что и запросы. Добавим актера в нашу базу данных:
mutation createActor {
createActor(input: {
name: "Tom Hanks"
}) {
ok
actor {
id
name
}
}
}
Обратите внимание, как input
параметр соответствует input
свойствам
Arguments
которые мы создали ранее.
Также обратите внимание, как ok
и actor
отображаются в свойствах
CreateActor
мутации CreateActor.
Теперь мы можем добавить фильм, в котором снимался Том Хэнкс:
mutation createMovie {
createMovie(input: {
title: "Cast Away",
actors: [
{
id: 3
}
]
year: 1999
}) {
ok
movie{
id
title
actors {
id
name
}
year
}
}
}
К сожалению, мы просто ошиблись. "Изгнанный" вышел в 2000 году!
Давайте запустим запрос на обновление, чтобы исправить это:
mutation updateMovie {
updateMovie(id: 2, input: {
title: "Cast Away",
actors: [
{
id: 3
}
]
year: 2000
}) {
ok
movie{
id
title
actors {
id
name
}
year
}
}
}
Там все исправлено!
Общение через POST
GraphiQL очень полезен во время разработки, но стандартной практикой является отключение этого представления в производственной среде, так как это может позволить внешнему разработчику слишком хорошо разбираться в API.
Чтобы отключить GraphiQL, просто отредактируйте
django_graphql_movies/urls.py
, чтобы
path('graphql/', GraphQLView.as_view(graphiql=True)),
стал
path('graphql/', GraphQLView.as_view(graphiql=False)),
Приложение, взаимодействующее с вашим API, будет отправлять запросы POST
в конечную точку /graphql
Прежде чем мы сможем отправлять запросы POST
извне сайта Django, нам нужно изменить django_graphql_movies/urls.py
:
from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView
from django_graphql_movies.schema import schema
from django.views.decorators.csrf import csrf_exempt # New library
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]
Django имеет встроенную защиту CSRF (подделка межсайтовых запросов) - в нем есть меры по предотвращению выполнения потенциально вредоносных действий некорректно аутентифицированными пользователями сайта.
Хотя это полезная защита, она помешает внешним приложениям взаимодействовать с API. Вам следует рассмотреть другие формы аутентификации, если вы запускаете свое приложение в производство.
В вашем терминале введите следующее, чтобы получить всех актеров:
$ curl \
-X POST \
-H "Content-Type: application/json" \
--data '{ "query": "{ actors { name } }" }' \
http://127.0.0.1:8000/graphql/
Вы должны получить:
{"data":{"actors":[{"name":"Michael B. Jordan"},{"name":"Sylvester Stallone"},{"name":"Tom Hanks"}]}}
Заключение
GraphQL - это строго типизированный язык запросов, который помогает создавать развивающиеся API. Мы разработали схему API для фильмов, создав необходимые типы, запросы и мутации, необходимые для получения и изменения данных.
С Graphene мы можем использовать Django для создания GraphQL API. Мы реализовали схему фильма, которую разработали ранее, и протестировали ее с помощью запросов GraphQL через GraphiQL и стандартного запроса POST.
Если вы хотите увидеть исходный код всего приложения, вы можете найти его здесь .