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

Общие стратегии

Стратегия №1 - Общий доступ к базе данных

Интегрировать системы по их базам данных кажется настолько простым делом, что мы чувствуем соблазн сделать это. Все, что нам нужно сделать, это разделить доступ к базам данных между службами, чтобы система A могла получить доступ к базе данных системы B и так далее. В чем проблема такого подхода? Вот список некоторых из них:

# 1.1 Производительность
Проблемы с производительностью в службе A могут повлиять на систему B и затруднить их независимое масштабирование, на самом деле они не независимы.

# 1.2 Дублированные бизнес-правила
Представьте, что система A имеет список пользователей со свойством статуса, которое указывает, активирован пользователь или нет, если служба B извлекает этих пользователей непосредственно из базы данных A, вам придется фильтровать по активированным пользователям в двух местах, если это правило изменится, вероятность того, что один из них устареет высока.

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

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

Стратегия №2 - синхронизировать все данные в централизованной базе данных

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

# 2.1 Различные типы источников данных
Если в вашей архитектуре есть компоненты с разными типами источников данных, например, некоторые из них используют PostgreSQL, MySQL другие используют MongoDB , Redis, Cassandra, Neo4j… Их все может быть сложно синхронизировать в единой централизованной базе данных из-за разных форматов.

# 2.2 Условия гонки
Для синхронизации требуется некоторое время, затем, когда служба пытается получить доступ к информации, которая еще не была синхронизирована, эта служба будет работать с устаревшей версией данных, и вы начнут возникать некоторые случайные проблемы. Распространенное решение - добавить некоторую задержку для запроса этой информации. На самом деле это не настоящее решение, потому что влечет за собой другие проблемы.

# 2.3 Изменения в схеме службы
Когда вы меняете схему какой-либо службы, она будет синхронизирована, а затем другие службы, использующие старый формат, выйдут из строя. Вы можете свести к минимуму эту проблему, написав гигантский интеграционный тест, который запускается после изменений в каждом компоненте, но изменение в одной службе не должно влиять на другую службу (см. Компонентизация через службы).

# 2.4 Изменение типа источника данных
Возможно, вам потребуется изменить тип источника данных, например, с документа на реляционный (здесь, в GetNinjas, у нас был случай вроде что). Это изменение повлияет на все службы, использующие эти данные.

# 2.5 Динамически вычисляемая информация
В некоторых случаях информация не сохраняется, а вычисляется перед использованием, например URL-адрес аватара пользователя. Этот тип информации находится внутри приложения в таких местах, как декораторы, презентаторы, файлы конфигурации, не сохраняется в базе данных и, очевидно, не будет синхронизироваться, поэтому в этой стратегии вам придется дублировать логику для достижения того же URL-адреса результата.

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

# 2.7 Формат данных
Рекомендуется хранить данные в необработанном виде (без формата) и форматировать их при отображении. Используя API для интеграции систем, вы также можете возвращать данные в формате, и логика форматирования этих данных не распространяется на всю архитектуру. В этой стратегии вам нужно будет сохранить отформатированные данные или повторно реализовать логику.

Синхронизация баз данных в одном большом месте может быть хорошим выбором для целей анализа, но не для обмена данными между службами.

Стратегия № 3 - API REST

На этом этапе все становится намного лучше. Но можно перечислить несколько проблем.

# 3.1 Трудно повторно использовать существующие конечные точки
Скорее всего, для каждой службы потребуется набор определенных полей, добавление новых полей в конечную точку не только повлияет на все службы, использующие эту конечную точку, но и также снизить производительность API.

# 3.2 Создайте определенные конечные точки для каждой службы
Кажется, это хорошая идея, но по мере того, как вы начинаете добавлять все больше и больше конечных точек, API становится беспорядочным, и время разработки также увеличивается. Необходимость в системе документации, подобной Swagger, становится очевидной.

В некоторых случаях вы можете перестать читать здесь, REST API действительно отлично справляются с интеграцией сервисов, НО GraphQL уже здесь, и понимание этого инструмента может быть очень полезным!

GraphQL как API для некоторых сервисов

Ниже я опишу несколько преимуществ GraphQL перед REST.

Готовлю пост о болевых точках GraphQL, следите за новостями :)

Обновление 19.09.17: Болевые точки GraphQL

Гибкость
Вы можете запрашивать схему, перемещаясь по нескольким ресурсам, по их отношениям, сохраняя циклические обращения к серверу.

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

Основные

  • Одновременный запрос нескольких «ресурсов» в одном запросе (в данном случае пользователя и репозитория).
  • Запрос точно соответствует ответу. Вам не нужно читать документацию или запускать запрос, чтобы узнать структуру ответа.
  • Вы можете передавать аргументы в поля (например, в avatarUrl, который получает size).
  • Запрашивать вложенные ресурсы (например, организации являются дочерними по отношению к пользователю).
  • Пагинация готова к использованию (см. Спецификация подключения курсора реле).

Документация
Поле документа за полем во время разработки.

Основные:

  • Описание типов или полей отображается вверху окна документации.
  • Система типов помогает вам определить вашу схему более естественным образом.
  • Вы можете пометить поля как устаревшие (например, databaseId типа организации).

Разработка
Развивайте всю схему API, а не только одну конечную точку REST API. Когда вы добавляете поле, его могут использовать другие потребители.

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

Мониторинг
Отслеживайте использование каждого поля в своем API, вы можете отслеживать, кто использует, производительность и использование устаревших полей.

Конечно, GraphQL не заменит все случаи, которые мы привыкли решать с помощью других технологий, их так много, RESTful, SOAP, Sockets, Protobuf, какой-то бинарный протокол поверх UDP, Очереди и т. Д. Знание других вариантов поможет вам быстрее и лучше достичь своих целей.

GraphQL как API-шлюз

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

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

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

Объединение API в одном месте

Обновление: 26.02.2018

На этом этапе вы можете спросить себя, как объединить все API-интерфейсы только в одну схему GraphQL? Есть несколько инструментов, которые помогут вам :)

Сшивание схемы GraphQL
Я предполагаю, что этот термин был создан командой Apollo. Это относится к действию слияния схем GraphQL.

Этот пост описывает, как работает сшивание схемы, а здесь вы можете найти примеры для его построения с помощью Apollo GraphQL Tools.

Еще один интересный инструмент для объединения схем GraphQL - это graphql-weaver (инструмент для объединения, связывания и преобразования схем GraphQL), который фактически родился до инструментов, созданных Apollo.

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

...
person: {
  type: PersonType,
  args: {
    id: { type: GraphQLString },
  },
  resolve: (root, args) => {
    fetch(`${BASE_URL}/person/${args.id}/`)
      .then(res => res.json())
      .then(json => json.person)
  },
},
...

Если вы решите пойти по этому пути, обратите внимание на Обертывание REST API в GraphQL и DataLoader (чтобы предотвратить выборку N + 1).

Заключение

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

Особая благодарность Cristhiane Almeida, Daniel Tamai и Ion D. Filho, которые помогли просмотреть более ранний черновик этого сообщения.

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