Статьи

Создание API с Ruby on Rails и GraphQL

Создание API с Ruby on Rails и GraphQL

Разработка API для мобильных и веб-приложений сегодня стала очень распространенной проблемой. Со времени появления смартфонов десять лет назад (а вместе с ними и взрыва мобильных приложений) API REST стали основным стандартом обмена данными между сервером приложений и клиентами.

Одна из самых больших проблем, возникающих при разработке API, — это структура и детализация данных, которые ваш сервер возвращает вашему клиенту. Допустим, вы создаете социальную сеть в стиле Twitter, где пользователи могут подписываться на других пользователей. При разработке вашего API вы можете добавить конечную точку ( GET /users/123 ) для получения данных о конкретном пользователе. Должен ли сервер отправлять данные о подписчиках этого пользователя в ответе API? Или он должен держать ответ очень легким, потому что вам нужны только основные данные об этом одном пользователе? Что если вашему клиенту нужны полные данные этого пользователя, а только имя пользователя и фотографии профиля его подписчиков?

Прямо сейчас вы можете подумать о некоторых хитростях, используя параметры запроса при вызове конечной точки, что-то вроде GET /users/123?full=true&with_followers=true&light_followers=true . Тем не менее, держу пари, вы легко поймете, как этот подход может стать огромным источником головной боли для вас и всех других инженеров, которым необходимо использовать ваш API.

Теперь представьте, что ваше приложение является чем-то таким сложным, как GitHub или Facebook, с чередованными данными между пользователями, сообщениями, отношениями и т. Д., И головная боль теперь превращается в кошмар.

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

Пусть клиент спросит, какие данные ему нужны от сервера.

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

 { user(id: 123) { id username, email, bio, profile_picture, followers(size: 20) { id username, profile_picture } } } 

API, реализующий протокол GraphQL, будет затем отвечать следующими данными JSON:

 { "user": { "id": 123, "username": "foo", "email": "[email protected]", "bio": "I'm just a sample user, nothing much to say." "profile_picture": "https://mycdn.com/somepicture.jpg", "followers": [ { "id": 134, "username": "bar", "profile_picture": "https://mycdn.com/someotherpicture.jpg" }, { "id": 153, "username": "baz", "profile_picture": "https://mycdn.com/anotherpicture.jpg" }, // and another 18 followers at maximum ] } } 

В этом уроке мы увидим, как мы можем реализовать простой API базы данных фильмов, используя язык GraphQL и Ruby on Rails.

Создание проекта

Мы создадим проект Rails, используя опцию --api чтобы использовать облегченную версию среды полного стека, содержащую только то, что нам нужно для построения REST API.

 $ gem install rails $ rails new graphql-tutorial --api $ cd graphql-tutorial/ 

Теперь добавьте gem- graphql в ваш Gemfile:

 gem 'graphql' 

И выполните $ bundle install для установки gem. Теперь мы можем запустить сервер приложений с сервером $ rails server .

Нам нужно сгенерировать две наши модели: одну для представления фильмов, а другую для актеров кино:

 $ rails g model Movie title:string summary:string year:integer $ rails g model Actor name:string bio:string 

Мы собираемся реализовать отношение «многие ко многим» между нашими двумя моделями, поэтому нам нужно сгенерировать миграцию для таблицы соединений:

 $ rails g migration CreateActorsMovies 

Затем обновите модели:

 # app/models/movie.rb class Movie < ActiveRecord::Base has_and_belongs_to_many :actors end # app/models/actor.rb class Actor < ActiveRecord::Base has_and_belongs_to_many :movies end 

Теперь у нас есть очень простая установка приложения Rails с двумя моделями. Давайте теперь создадим наш API, реализующий схему GraphQL.

Построение схемы GraphQL

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

 { movie(id: 12) { title year actors(size: 6) { name } } } 

Давайте разберем его на следующие части:

  • Внутри первого открывающего и последних закрывающих скобок находится тело нашего запроса GraphQL, также называемое корневым объектом или объектом запроса . Этот объект имеет одно поле movie и принимает id одного аргумента. API отвечает за возврат с объектом фильма с указанным id .
  • В этом полевом movie мы просим указать title и year скалярных полей, а также поле сбора actors . Для этого последнего мы даем аргумент size , определяя, что мы хотим, чтобы только первые 6 актеров были связаны с этим фильмом.
  • Наконец, мы запрашиваем одно name поля для каждого актера в коллекции, содержащейся в поле actors .

Теперь, когда мы кратко рассмотрели возможности языка GraphQL, мы можем начать реализацию с определения корневого объекта запроса:

 # app/types/query_type.rb QueryType = GraphQL::ObjectType.define do name "Query" description "The query root for this schema" field :movie do type MovieType argument :id, !types.ID resolve -> (obj, args, ctx) { Movie.find(args[:id]) } end field :actor do type ActorType argument :id, !types.ID resolve -> (obj, args, ctx) { Actor.find(args[:id]) } end end 

Корневой объект может иметь два непосредственных дочерних типа, которые являются двумя моделями, которые мы определили в нашем приложении: movie и actor . Для каждого определения поля укажите его тип, который будет определен в его собственном классе позже. Мы также указываем аргументы, которые может принимать поле — только id имеющий специальный ID! типа ID! ( ! указывает на то, что этот аргумент обязателен).

Наконец, мы предоставляем функцию resolve , где мы возвращаем значение идентификатора, указанное в запросе, и возвращаем связанную модель из базы данных. Обратите внимание, что в нашем случае единственное, что мы делаем, — это вызываем метод Active Record Actor::find , но можем свободно реализовывать все, что захотим, пока мы возвращаем запрошенные данные.

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

 # config/application.rb config.autoload_paths < < Rails.root.join("app", "types") 

Нам все еще нужно определить типы для обработки полей movie и actor . Вот код для определения MovieType :

  # app/types/movie_type.rb MovieType = GraphQL::ObjectType.define do name "Movie" description "A Movie" field :id, types.ID field :title, types.String field :summary, types.String field :year, types.Int field :actors do type types[ActorType] argument :size, types.Int, default_value: 10 resolve -> (movie, args, ctx) { movie.actors.limit(args[:size]) } end end 

Это определение аналогично предыдущему, за исключением title , summary и year скалярных полей. Нам не нужно предоставлять метод resolve , так как библиотека выведет поля модели по их имени. Тип поля actors[ActorType] , который является коллекцией объектов типа ActorType .

Мы также указываем, что actors поля может быть предоставлен необязательный аргумент size . Определение метода resolve позволяет нам обрабатывать его значение с помощью API Active Record, чтобы ограничить размер коллекции.

Определение ActorType приведенное ниже, выполняется очень похожим образом:

 # app/types/actor_type.rb ActorType = GraphQL::ObjectType.define do name "Actor" description "An Actor" field :id, types.ID field :name, types.String field :bio, types.String field :movies do type types[MovieType] argument :size, types.Int, default_value: 10 resolve -> (actor, args, ctx) { actor.movies.limit(args[:size]) } end end 

Наконец, мы можем создать схему, содержащую корневой объект запроса в качестве члена запроса:

 # app/types/schema.rb Schema = GraphQL::Schema.define do query QueryType end 

Теперь мы можем использовать его как точку входа для наших запросов внутри контроллеров API:

 # app/controllers/movies_controller.rb class MoviesController < ApplicationController # GET /movies def query result = Schema.execute params[:query] render json: result end end 

Теперь мы готовы использовать запросы GraphQL внутри нашего API! Давайте добавим некоторые данные …

 $ bundle exec rails c > movie = Movie.create!(title: "Indiana Jones", year: 1981, summary: "Raiders of the Lost Ark") > actor = Actor.create!(name: "Harrison Ford", bio: "Some long biography about this actor") > movie.actors < < actor 

… и попробуйте конечную точку, используя любой HTTP-клиент:

 curl -XGET http://localhost:3000/movies -d "query={ movie(id: 1) { title, year, actors { name } } }" 

Идти дальше

Для тех из вас, кто хотел бы продолжить изучение использования GraphQL с Rails, вот несколько интересных вещей:

  • На данный момент мы можем запросить только конкретный фильм по его идентификатору, так как мы определили поле особого movie . Что если бы мы хотели получить список фильмов, выпущенных в 1993 году? Мы могли бы добавить еще одно поле movies в наш корневой Query , используя для фильтрации аргументы фильтра, такие как year и возвращать список записей:

     ```ruby # app/types/query_type.rb field :movies do type types[MovieType] argument :year, types.Int resolve -> (obj, args, ctx) { if args[:year].present? Movie.where(year: args[:year]) else Movie.all end } end ``` 
  • Мы могли бы позволить клиенту указать порядок, в котором возвращаются списки actors или movies , на случай, если у нас будет много записей. Это можно сделать снова, используя необязательные аргументы и обрабатывая их внутри функции resolve .

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

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

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


Дальнейшее чтение на SitePoint