Такие сторонние библиотеки, как Apollo iOS client и AWS AppSync нужны для связи с сервером GraphQL в iOS-приложении, но они также вводят в ваш проект лишний код.
Клиент Apollo iOS нормализует результаты запросов для построения клиентского кэша с помощью базы данных SQLite. АWS AppSync снабжает клиент Apollo некоторыми дополнительными функциями для выполнения проверки подлинности AWS.
Я всегда чувствую себя неуверенно, когда связываю свой проект со сторонними библиотеками.
Поводы для беспокойства здесь следующие:
- Насколько безопасна библиотека?
- Что если библиотеку перестанут поддерживать?
- Сколько времени уходит у команды поддержки на то, чтобы исправить ошибку и выпустить патч?
- Кто же сообщает клиентам, что не может исправить ошибку по вине третьей стороны?
По счастью, есть простой и легкий способ выполнять GraphQL с помощью необработанного HTTP-запроса.
В этой статье продемонстрирую его пошаговую настройку (на основе Star War GraphQL API).
1.Операция query
В GraphQL есть три типа операций: query
, mutation
и subscription
.
Запрос query
извлекает данные из API. Это можно выполнить HTTP-методом GET
или POST
в зависимости от API GraphQL.
Для API Star War GraphQL применяется метод POST
.
Далее приведен пример формы для стандартного POST
-запроса GraphQL. Тип содержимого в этом запросе — application/json
. А форма — это его тело, закодированное в JSON-формате.
{
"query": "...",
"variables": { "myVariable": "someValue", ... }
}
Начнем со строки query
.
2.Строка query
В теле запроса это выглядит так:
let query: String =
"""
query allFilms($filter: FilmFilter) {
allFilms(filter: $filter) {
title
director
starships {
pilots {
name
}
}
}
}
"""
- Здесь определяется строка запроса на получение фильмов с помощью фильтра.
- Ключевое слово
query
в строке определяет тип операции, которая будет выполнена. allFilms
— это название операции. Оно не обязательно, но значимое и недвусмысленное имя помогает идентифицировать операцию.$filter
— это одна из переменных, принимаемых запросом, за которой следует тип фильтраFilter
. Можно также передать другую переменную, а не создавать ее в запросе.- Блок
fields
описывает конкретные поля, которые мы запрашиваем, и имеет ту же форму, что и то, что мы получаем из API GraphQL.
{
title
director
starships {
pilots {
name
}
}
}
3.Кодируемые переменные
Как сказано в документации Star War GraphQL API, дополнения запроса allFilms
таковы:
allFilms(
filter: FilmFilter
orderBy: FilmOrderBy
skip: Int
after: String
before: String
first: Int
last: Int
): [Film!]!
Они полностью опциональны. Продемонстрируем на примере Filter
:
struct Filter: Encodable {
let director: String
}
struct Variable: Encodable {
let filter: Filter
}
let filter = Filter(director: "Irvin Kershner")
let variable = Variable(filter: filter)
У Filter
, в свою очередь, тоже существует несколько опциональных свойств. Мы берем director
только для упрощения демонстрационного кода.
Структуры Filter
и Variable
принимают протокол Encodable
для подготовки тела в формате JSON.
4.Создание HTTP-запроса
struct Payload<T: Encodable>: Encodable {
let variables: T
let query: String
}
let payload = Payload(variables: variable, query: allFilmsQuery)
var request = URLRequest(url: swapiURL)
request.httpBody = try! JSONEncoder().encode(payload)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {
return print("data is null")
}
let swiftyJsonVar = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
print(swiftyJsonVar)
}.resume()
struct Payload<T: Encodable>: Encodable {
let variables: T
let query: String
}
let payload = Payload(variables: variable, query: allFilmsQuery)
var request = URLRequest(url: swapiURL)
request.httpBody = try! JSONEncoder().encode(payload)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {
return print("data is null")
}
let swiftyJsonVar = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
print(swiftyJsonVar)
}.resume()
Давайте разбираться:
- Структура
Payload
содержитvariables
иquery
. Она также перенимает протоколEncodable
. request
представляет собой экземплярURLRequest
, встроенный в Star War API URL: https://swapi.graph.cool.payload
кодируется в формате JSON и добавляется к телу запросаrequest
.- Метод
POST
задействован согласно требованию Star War GraphQL API. - Для значений
Content-Type
andAccept
в поле заголовка HTTP используетсяapplication/json
. request
просто запускается одновременно с общим экземпляромURLSession
.- Ответные данные находятся в формате JSON и могут быть десериализованы посредством
JSONSerialization
.
Заключение
В этой статье описывается наиболее облегчённый способ взаимодействия со службой GraphQL. Необработанный HTTP-запрос устраняет необходимость каких бы то ни было сторонних библиотек. Быстрый и чистый код целиком находится под контролем разработчика.
Ресурсы
Все сторонние библиотеки, API GraphQL и образцы кода Swift можно найти по следующим ссылкам:
Читайте также:
- Решаем проблему запроса N+1 в GraphQL с помощью Dataloader
- Добавление отношений в схему GraphQL
- Почему нельзя разрешать поля GraphQL как конечные точки REST
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи: Eric Yang, “The Easiest Way to Perform GraphQL Queries in iOS”