Обратите внимание, что мы также размещаем этот пост в нашем блоге о создании новостного сайта i.stuff.co.nz: https://technology.fairfaxmedia.co.nz/taming-rest-apis-using-graphql /

В Stuff, как и во многих других командах разработчиков, мы в настоящее время направляем свои усилия на создание микросервисов.

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

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

Если вы (как и мы) используете несколько REST API, некоторые из вопросов, которые возникают в рамках этого шага, вероятно, будут включать следующее:

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

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

Недостаточное обслуживание

Недостаточное обслуживание возникает, когда необходимые данные можно получить только путем вызова нескольких конечных точек REST. В качестве примера рассмотрим следующее:

curl -X GET http://example.com/api/user/{userid}

Эта конечная точка может возвращать что-то вроде следующего:

{
    "id": 1,
    "username": "jcdarwin",
    "first_name": "Jason",
    "last_name": "Darwin"
}

Чтобы найти домашних животных Джейсона, нам, возможно, придется сделать отдельный вызов REST:

curl -X GET  http://example.com/api/user/{userid}/pets
{
    "pets": [
        {
            "id": 1,
            "name": "Minty",
            "species": "cat" 
        },
        {
            "id": 2,
            "name": "Ari",
            "species": "cat" 
        }
    ]
}

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

curl -X GET http://example.com/api/user/{userid}/pets/1
{
    {
        "id": 1,
        "name": "Minty",
        "species": "cat",
        "favourite_food": "sardines",
        "last_vet_visit": "2016_09_11" 
    }
}
curl -X GET http://example.com/api/user/{userid}/pets/2
{
    {
        "id": 2,
        "name": "Ari",
        "species": "cat",
        "favourite_food": "mice",
        "last_vet_visit": "2016_11_25" 
    }
}

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

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

Чрезмерное обслуживание

Избыточное обслуживание происходит, когда вызов API возвращает больше данных, чем вам нужно, так что есть потери с точки зрения сети (и, возможно, ресурсов ЦП, если данные REST особенно большие / сложные). Возвращаясь к нашему предыдущему примеру, если бы мы хотели только определить дату последнего посещения ветеринара нашим питомцем, мы также получили бы избыточную информацию, такую ​​как любимая еда питомца:

curl -X GET http://example.com/api/user/{userid}/pets/1
{
    {
        "id": 1,
        "name": "Ari",
        "species": "cat",
        "favourite_food": "mice",
        "last_vet_visit": "2016_11_25" 
    }
}

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

Наша идеальная ситуация

В идеале, чтобы максимально эффективно использовать ресурсы (сеть и ЦП) и минимизировать объем специфичной для API логики, которую нам нужно написать, нам нужно что-то вроде следующего:

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

GraphQL

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

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

Как утверждает официальный сайт GraphQL, GraphQL можно описать следующим образом:

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

Служба GraphQL создается путем определения типов и полей для этих типов, а затем предоставления функций для каждого поля для каждого типа ».

и в другом месте:

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

Когда мы создавали мобильные приложения Facebook, нам требовался API для извлечения данных, достаточно мощный, чтобы описать весь Facebook, но при этом достаточно простой, чтобы его было легко изучить и использовать разработчикам наших продуктов. Мы разработали GraphQL в 2012 году, чтобы удовлетворить эту потребность. Сегодня это основной способ создания клиентских приложений и серверов, которые ими управляют ».

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

Наш запрос GraphQL может выглядеть следующим образом:

users(id: 1) {
    pets() {
        id
        last_vet_visit 
    }
}

В то время как возвращаемые данные (как обычный JSON) могут выглядеть следующим образом:

"data": {
    "users": [
        {
            "pets": [
                {
                    "id" : 1,
                    "last_vet_visit": "2016_09_11" 
                },
                {
                    "id" : 2,
                    "last_vet_visit": "2016_11_25" 
                }
            ]
        }
    ] 
}

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

curl -X GET http://example.com/api/user/{userid}/pets
curl -X GET http://example.com/api/user/{userid}/pets/1
curl -X GET http://example.com/api/user/{userid}/pets/2

Однако, вероятно, гораздо лучше, если эти различные вызовы REST API выполняются на стороне сервера, где пропускная способность сети и ЦП обычно намного лучше, чем на клиенте.

Итак, наше использование GraphQL может выглядеть примерно так:

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

Сравнение вызовов API внешнего интерфейса и внутреннего интерфейса

При рассмотрении того, как развернуть сервер API на основе GraphQL, нам необходимо определить, как лучше всего его раскрыть.

Например, если мы используем React для выполнения рендеринга компонентов на стороне клиента, получения данных для компонентов с нашего сервера API GraphQL через HTTP-вызовы (то есть в браузере), тогда нам нужно тщательно подумать о таких проблемах, как безопасность и авторизация, а также некоторые проблемы, возникающие из-за того, что делает возможным наш сервер GraphQL:

  • учитывая, что запросы GraphQL могут быть рекурсивно вложенными (например, если мы хотим получить наших друзей и друзей каждого из наших друзей, использующих один и тот же базовый REST API), один запрос GraphQL может представлять довольно большую активность для базовых REST API. , он же DDOS по запросу
  • учитывая, что один запрос GraphQL может обернуть несколько REST API, нам может потребоваться подумать о детализации аутентификации / авторизации.
  • учитывая, что один запрос GraphQL может обернуть несколько REST API, имеет смысл подумать о том, где разместить слой кеширования.

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

На приведенной выше диаграмме слева показан рендеринг серверной части, при котором мы создаем наши компоненты внешнего интерфейса на стороне сервера, а затем передаем их на страницу в виде полностью сформированного HTML с помощью Edge Side Includes.

В этом сценарии браузер не знает и не имеет доступа ни к нашему серверу компонентов React, ни к нашему серверу API GraphQL.

Однако, если мы рассмотрим рендеринг внешнего интерфейса (правая часть приведенной выше диаграммы), наш браузер (и любой другой гнусный агент в Интернете) имеет некоторые сведения и имеет доступ как к нашему серверу компонентов React, так и к нашему серверу API.

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

  • Возможность взаимодействия клиентов с API-интерфейсами, что особенно важно при отправке форм.
  • Обслуживаемый контент может быть более актуальным, чем с нашими включениями ESI, которые обычно генерируются только каждые несколько минут.

Заворачивать

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

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