Основная комплексная реализация GraphQL с помощью Express (JS) и Android-Kotlin с сопрограммами (часть 1)

Я решил изучить, что такое GraphQL, чтобы собрать знания об этой технологии и иметь возможность принимать обоснованные решения относительно будущего API в моей профессии.

Весь код вы найдете в моем репозитории GitHub

В этой статье я не буду подробно объяснять, что это такое и что с ним можно делать, и не буду сравнивать его с традиционным REST-подходом. Для этого я приведу ссылки на множество ресурсов внизу этой статьи, где вы можете пойти и почитать людей, которые провели обширные исследования и создали потрясающую документацию.

Я решил получить знания об этом, стремясь реализовать комплексное решение самостоятельно, то есть создать веб-сервер, который может принимать запросы GraphQL, очень простую базу данных и клиент Android, который выполняет запросы к этому серверу и обрабатывает ответы.

Моя цель — создать Hello World Android и GraphQL.

В моем исследовании я получил от GraphQL в основном следующее:

  • Сторона сервера предоставляет данные и все возможные точки входа в эти данные.
  • Клиент сам решает, какие данные получать и как выполнять запросы

Это немного контрастирует с некоторыми подходами, которые я видел в REST, где серверная сторона решает от имени клиента, какие данные нужны клиенту и какими должны быть запросы. Например, вы собираетесь сделать один запрос на весь ваш экран, а мы вам скажем, какие данные вам нужны.

С GraphQL это немного наоборот, серверная сторона говорит: «Эй, я получил эти данные, и вы можете выполнять эти запросы», а затем клиент самостоятельно решает, какие данные ему нужны для его экрана, и хочет ли он выполнить один или несколько запросов. .

Это особенно заметно, когда разные клиенты (Android, iOS, телефон, планшет, Roku, телевизоры Samsung) имеют более или менее одинаковые функции, но на самом деле требуют разных данных.

Я также обнаружил, что, вероятно, все, что вы можете сделать с GraphQL, вы можете сделать и с REST. GraphQL навязывает вам конкретную методологию и процесс, в то время как REST зависит от вас.

В качестве примера с GraphQL контракт между клиентом и сервером программно закодирован в файле схемы, и это универсальный источник правды, вы не можете избежать этого, вы не можете подделать его, вы не можете делать с ним трюки, вы не можете делать вид, что вы иметь схему, а затем нарушить ее, поскольку она не будет просто компилироваться.

С REST вы можете работать без схемы, если хотите, вы можете иметь схему, если хотите, с Swagger. Вы можете применить его, или нет. Вы можете солгать, вы можете сказать, что что-то является обязательным, когда это не так, и вы можете провалить некоторые тесты, если вы это сделаете. Схема не является частью процесса.

Кроме того, GraphQL не является графовой базой данных. С этим тоже есть некоторая путаница. GraphQL — это просто точка входа для запросов API, вот и все. Вы можете использовать базу данных в памяти или SQL или сделать REST API где-то еще. Вы можете использовать его для единой точки входа для всех ваших микросервисов, если хотите.

После этих основных моментов давайте перейдем к коду. Я буду реализовывать следующее:

  • Веб-сервер с Express Server (то есть JavaScript с node.js), который работает локально на моем компьютере.
  • База данных в памяти, где мы можем делать запросы к нашим данным
  • Клиент Android, который будет выполнять необработанные запросы GraphQL без каких-либо сторонних библиотек, написанный на Kotlin и сопрограмм, работающий на локальном эмуляторе с очень простым пользовательским интерфейсом.

Код будет минимальным и совсем не исчерпывающим, он служит в качестве исследования и вообще не предназначен для производственных целей. На моей машине установлена ​​macOS Mojave 10.14.2.

Сервер

Мы будем использовать npm для установки всех наших зависимостей.

Как только все зависимости будут установлены, создайте файл server.js, в котором мы будем кодировать наш сервер.

После этого мы закодируем наш метод запуска сервера в виде скрипта в файле package.json следующим образом:

С помощью команды npm start сервер будет запущен и запущен, но он будет работать должным образом только после того, как мы определим код для запуска сервера, схему и запросы, которые он может обрабатывать.

Реализация сервера (server.js)

Наш сервер для работы должен запустить экспресс-приложение, модуль GraphQL, чтобы он мог понимать запросы GraphQL, и ему нужно определение схемы GraphQL, чтобы он знал, как обрабатывать запросы.

Я также активирую функцию graphiql на сервере, чтобы у нас был очень приятный пользовательский интерфейс, в котором мы могли бы выполнять запросы к серверу без реализации какого-либо клиента.

Что происходит, так это то, что сервер представляет собой экспресс-приложение, которое использует любой запрос к пути /graphql для перенаправления в модуль graphql, который будет использовать schema, определенный в ./schema/schema (еще не сделано), и что функция graphiql включена.

Сам сервер будет прослушивать локальный порт 4000 и распечатывать журнал в командной строке, когда он активен.

После определения файла schema сервер сможет принимать запросы.

Реализация файла схемы

Файл схемы представляет собой программную кодировку типов запросов, которые понимает сервер, и типов объектов, которые сервер может обрабатывать. Например, запрос будет заключаться в получении книги с идентификатором, где книга является определенным типом в схеме.

Для нашего примера мы будем кодировать именно это, запросы к книгам. Для справки, запросы, типы (модели), базы данных или что-то еще на самом деле не обязательно должны быть в одном файле; было бы хорошей практикой разделить его на разные файлы.

Определения модели

GraphQL с Express определяет свои собственные примитивы как типы. Это значит, что String как примитива нет, будет GraphQLString. Пользовательские типы станут частью общего типа GraphQLObjectType, который будет содержать другие пользовательские или примитивные типы.

Основная идея определения модели состоит в том, чтобы определить следующее:

  • Внутреннее имя вашего типа, например, чтобы другие типы или запросы могли ссылаться на этот новый
  • Публичное имя вашего типа, например, чтобы когда вы делаете запрос к серверу, вы могли использовать в нем это имя
  • Поля вашего типа, например, у книги будут id, название, автор и т.д.

Вот что мы собираемся определить для нашей книжной модели:

Чтобы на самом деле это работало, мы должны загрузить библиотеки, необходимые для типов, которые мы только что использовали, поэтому в верхней части файла мы определим следующее:

То есть из модуля GraphQL мы собираемся использовать эти типы.

База данных

Для запроса данных мы можем сделать запрос SQL, или использовать MongoDB, или выполнить любой запрос REST, или сделать что угодно. Для этого примера и для того, чтобы что-то работало как можно быстрее, я буду кодировать базу данных в памяти. Кроме того, поскольку вы можете использовать любую базу данных с GraphQL, на самом деле это не дает большой пользы, чтобы тратить много времени на углубленное изучение правильной реализации базы данных.

Есть более сложные аспекты загрузки данных с помощью GraphQL, которые важны для GraphQL, но об этом я мог бы рассказать в других статьях.

На данный момент это база данных в памяти, которую я создал:

Модуль lodash поможет мне сделать запрос к этому массиву, используя поле. Например, выполнение lodash.find(books, '2') вернет вторую строку. Вы можете, конечно, реализовать это самостоятельно, если хотите.

Определения запросов

После того, как мы определили наши типы и базу данных, последним шагом будет фактическое создание точки входа для нашего запроса данных. Запрос определяет, какие типы будут обрабатываться и как удовлетворить запрос пользователя. Один интересный момент заключается в том, что вы можете создать циклическую зависимость, если хотите. Например, книга принадлежит автору, и одновременно у автора несколько книг. Вы можете создать запрос, который возвращает всю эту информацию, просто определяя запросы к вашим объектам, таким как авторы/книги, и связывая их в определении запроса. GraphQL организует все это за вас.

Запрос определяется следующим:

  • Новый объект запроса с внутренним именем, которое вам нравится (поскольку его можно использовать для ссылок в других запросах).
  • Поля запроса, каждое поле будет состоять из
  • Публичное имя
  • К какому типу модели относится
  • Аргументы вашего запроса, которые должны соответствовать любому полю вашей модели, например, id
  • И resolver. Это суть вашего запроса. Преобразователь — это фрагмент кода, который будет извлекать запрошенные данные. В этом случае, поскольку запрос относится к книге, а аргументом запроса является идентификатор, здесь нам нужно будет реализовать запрос к базе данных для получения этой конкретной книги.

Вот полная реализация:

Пример фактического запроса от клиента, который соответствует этому определению, будет выглядеть так:

Это первая строка book(id: "2") — это то, что мы определили в RootQuery, а args — это id 2.

Следующие поля name genre не определены в запросе, так как они импортируются из самого BookType. Это поля, которые клиент хочет от этой книги. Обратите внимание, что нет никакого кода для работы с такого рода полем filter; GraphQL сделает всю тяжелую работу за вас.

resolver использует lodash, чтобы вытащить id из предопределенного args.

Последний шаг — экспортировать этот запрос как часть того, что может обработать GraphQL. Теперь он будет содержать только один запрос, но может содержать все запросы, которые может обработать ваша система.

Запустить все это

Теперь у нас есть все необходимое для отправки некоторых запросов на наш первый сервер GraphQL.

Из командной строки запустим наш сервер

Если все правильно и на месте, вы должны увидеть что-то вроде этого:

Это будет слушать наш localhost, порт 4000 под адресом /graphql, в любом веб-браузере перейдите на http://localhost:4000/graphql и вы увидите пользовательский интерфейс graphiql.

Обратите внимание, что справа у вас есть документация. Это здорово, потому что вы до сих пор не писали никакой документации для своей новой конечной точки, но GraphQL генерирует ее для вас бесплатно в сочетании с вашей фактической реализацией.

Слева вы можете создать свой первый запрос:

И если вы нажмете кнопку воспроизведения, то вы выполните его, и ваш сервер ответит справа. Вы можете изменить id или поля, которые хотите запросить. Это просто работает, без лишнего кода, это круто.

Я рекомендую вам взять эту базовую реализацию и создать больше запросов и типов. Вы увидите, как легко создавать сложные запросы с минимальным набором кода.

В следующем посте я реализую клиентскую сторону Android этого проекта.

Ресурсы