Предприятия имеют разнообразные зависимости от данных — внутренние микросервисы со специализированными доменами данных, устаревшие системы с собственными форматами данных, а также сторонние API и приложения SaaS со своими уникальными моделями данных и конечными точками.
TL;DR: разные (и часто устаревшие) технологии, которые нужно как-то объединить.
Federated GraphQL выделился как главное решение для такого объединения в сфере предприятий, и Router (или Gateway) в Federation действует как ключевой элемент, который связывает все эти разрозненные источники данных вместе, делая их доступными через единственный, согласованный API, сохраняя при этом адаптивность. Это, на самом деле, ключ к тому, как Federated GraphQL позволяет создавать масштабируемые и модульные архитектуры.
Сегодня мы рассмотрим высокопроизводительный, открытый, совместимый с Federation V1/V2 Router от WunderGraph Cosmo. Мы расскажем, что он делает, почему он так важен для стека Cosmo, как вы можете разместить его самостоятельно, а также настроить и расширить его с помощью своего собственного кода на Go.
WunderGraph Cosmo — Введение
WunderGraph Cosmo — это полностью открытая (лицензия Apache 2.0) платформа для создания, управления и совместной работы над федеративными графами в масштабе. Это замена Apollo GraphOS/Studio, и это все в одном решении, которое содержит реестр схем, который может проверять наличие нарушений и ошибок композиции, очень быстрый Router, совместимый с Federation V1/V2, и платформу для аналитики/распределенного трассирования для федеративного GraphQL.
GitHub - wundergraph/cosmo: Открытая альтернатива Apollo GraphOS
Стек Cosmo учитывает все мнения и оптимизирован для максимальной эффективности и производительности. Он включает в себя:
Studio (веб-интерфейс GUI) и
wgc
(инструмент командной строки) для управления платформой в целом — схемы, пользователи и проекты — и доступа к аналитике/трассировкам.Router в качестве компонента, который фактически реализует GraphQL Federation, маршрутизирует запросы и агрегирует ответы.
Control Plane, сердце платформы, предоставляющее основные API, которые используют как Studio/CLI, так и Router.
В качестве хостинга вы можете запустить всю платформу на месте на любом сервисе Kubernetes (AWS, Azure, Google для производства и Minikube для локальных разработок) или использовать управляемое облако Cosmo Cloud для всех компонентов, которые имеют состояния; в то время как вы самостоятельно размещаете router без состояний.
Router Cosmo
Router Cosmo (который вы можете найти в монорепозитории Cosmo) — это открытая (лицензия Apache 2.0) альтернатива Apollo Gateway или Router. Это HTTP-сервер, написанный на Golang, который является центральной точкой входа для ваших федеративных архитектур GraphQL и отвечает за маршрутизацию запросов к нужным микросервисам/субграфам, агрегирование их ответов и отправку их обратно клиенту в едином формате. Вы даже можете настроить его с помощью собственного кода на чистом Go.
Router совместим с Apollo Federation v1 и v2, а также с Open Federation, открытой спецификацией для федеративного GraphQL.
1. Как это работает
Клиенты отправляют запросы GraphQL на конечную точку Router-а, и он интеллектуально маршрутизирует эти запросы к сервисам, которые могут их обработать. Вот что происходит, когда Router получает запрос от клиента.
Прежде всего, ему нужен доступ к схеме федеративного графа, которая включает типы, поля и метаданные каждой схемы подграфа, а также их конечные точки. Это периодически извлекается из CDN с высокой доступностью, чтобы убедиться, что у Router есть последняя конфигурация.
Он использует эту конфигурацию для генерации оптимизированного плана запросов — кэшированного по запросам, чтобы минимизировать выполненную работу — чтобы определить, как лучше всего разложить клиентский запрос на несколько подзапросов, какие подзапросы можно выполнить параллельно и какие должны выполняться последовательно, а также как объединить их ответы.
Затем Router использует план запросов для выполнения необходимого разложения на несколько подзапросов и их выполнения. Каждый подзапрос соответствует микросервису/подграфу, который может выполнить часть общего запроса.
Наконец, Router собирает частичный результат из каждого из этих подзапросов, объединяет и собирает их в согласованный ответ, который соответствует структуре исходного клиентского запроса — отправляя обратно этот собранный ответ клиенту.
Router делает так, что этот процесс полностью непрозрачен для клиента. Насколько последний знает, Router был единственной конечной точкой, к которой обращались за данными, а федеративная схема была единственным действующим API.
2. Производительность
Router Cosmo работает на основе graphql-go-tools, высокоразвитого и оптимизированного движка GraphQL (лицензия MIT), который является самой быстрой и надежной реализацией для Federation V1. Router Cosmo строится на нем со своими собственными оптимизациями.
Кроме того, сам Go разработан для производительности, имеет небольшой объем памяти и быструю скорость выполнения, и компилируется непосредственно в машинный код (в отличие от, например, NodeJS, который зависит от движка JavaScript V8 и имеет некоторые накладные расходы).
Давайте посмотрим на два способа, благодаря которым Router Cosmo получает большие приросты производительности.
Группировка
Когда вы запрашиваете данные, которые включают вложенные отношения между разными сервисами (и, таким образом, удаленные соединения), скажем, Пользователь и его Сообщения, наивная реализация делает один отдельный запрос для разрешения поля posts
для каждого Сообщения Пользователя.
query {
allUsers {
id
name
posts {
id
title
content
}
}
}
Почему это неэффективно? Ну, сколько раз вы на самом деле обращаетесь к источникам данных?
Вы извлекаете всех Пользователей в одном запросе (это 1 запрос к сервису Пользователей).
Для каждого пользователя вы делаете отдельный запрос, чтобы получить его Сообщения (это еще N запросов к сервису Сообщений, где N — это количество Пользователей).
Так что, если у вас 100 пользователей, то это 1 + 100 = 101 вызов. Это известная проблема N+1. С GraphQL мы перенесли ее на сервер, а не на клиент, но она существует в любом случае и может замедлить извлечение данных до ползучего темпа, по мере роста числа Пользователей или если у вас есть дальнейшие вложенные данные.
Router Cosmo использует настроенный шаблон DataLoader (если вы не знакомы с базовой реализацией, смотрите видео Ли Байрона здесь.) для решения этой проблемы, прямо из коробки. Вместо того чтобы делать отдельные запросы для каждого Пользователя, Router анализирует клиентский запрос перед выполнением, чтобы определить, какие ключи соединения будут требоваться для разрешения удаленного отношения Сообщений, и изменяет первый подзапрос (извлечение всех Пользователей) также для извлечения этих — в данном случае наш ключ соединения будет author_id
.
# TL;DR: Вместо…
SELECT * FROM Posts WHERE author_id = 1;
SELECT * FROM Posts WHERE author_id = 2;
…
SELECT * FROM Posts WHERE author_id = N;
# DataLoader позволяет вам делать…
SELECT * FROM Posts WHERE author_id IN (1, 2, …N);
Насколько эффективен этот новый метод? Давайте посмотрим, сколько запросов вы делаете сейчас:
Как обычно, вы извлекаете всех Пользователей (это 1 запрос).
Теперь Router Cosmo группирует извлечение Сообщений для всех 10 пользователей в один запрос (это еще 1 запрос).
Итак, в общей сложности вы теперь делаете только 1 + 1 = 2 запроса для извлечения всех необходимых данных, независимо от количества Пользователей. Значительное улучшение по сравнению с наивным подходом N+1.
DataLoader от Cosmo - это специализированная реализация, которая делает гораздо больше, чем сказанное выше. В серверах GraphQL поведение по умолчанию - разрешать поля сначала в глубину - как проходить по списку элементов один за другим, исчерпывающе разрешая каждый элемент, прежде чем переходить к следующему. Вместо этого, шаблон DataLoader 3.0 от Cosmo разделяет традиционный процесс разрешения на два отдельных шага:
Загрузка данных сначала в ширину: на первом шаге система проходит по плану запроса сначала в ширину. Это означает, что она определяет все данные, необходимые от подграфов по всем полям, загружает их, а затем объединяет результаты, собранные из этих подграфов, в один объект JSON.
Генерация ответа сначала в глубину: после объединения данных в один объект JSON второй шаг включает проход по этому объединенному объекту JSON сначала в глубину - создание ответа JSON для клиента, в соответствии со структурой полученного от него запроса GraphQL. Этот шаг, по сути, собирает окончательный ответ, который будет отправлен обратно клиенту.
Вместо того, чтобы углубляться в каждую ветвь до ее самого глубокого уровня, прежде чем переходить дальше, подход Cosmo собирает данные из соседних полей одновременно. Это означает, что по мере прохождения запроса он собирает данные из нескольких элементов (соседей) на одном и том же уровне в структуре дерева одновременно.
Поскольку мы обрабатываем соседние поля вместе, мы можем автоматически группировать запросы для этих полей. Эта группировка уменьшает количество отдельных запросов, сделанных для извлечения данных, что приводит к значительному улучшению эффективности и производительности при разрешении запросов GraphQL по сравнению с базовой реализацией DataLoader, и специально оптимизирована для разрешения глубоко вложенных отношений - что приводит к огромным приростам производительности.
Безумный Режим
Когда несколько одинаковых запросов (запрашивающих одни и те же данные) находятся "в полете" одновременно (обрабатываются одновременно), Router Cosmo разумно отправляет только один запрос на исходный сервер, а результат разделяет со всеми остальными активными запросами.
Cosmo называет это "Безумный Режим" для Router, и когда он включен, он устраняет избыточные запросы на одни и те же данные, оптимизирует сетевой трафик и уменьшает нагрузку на исходный сервер.
Хотя Безумный Режим очень ситуативен и, конечно, не устраняет необходимости обрабатывать каждый клиентский запрос индивидуально, он особенно ценен при работе с часто выполняемыми, только для чтения запросами (например, разрешение вложенных списков сущностей).
3. Отсутствие состояния
Поскольку сам Router не хранит никаких специфических для сессии данных между запросами, каждый запрос, который он обрабатывает, является независимым. Таким образом, можно предусмотреть несколько экземпляров Router в соответствии с вашими требованиями, и построение вашей федеративной архитектуры GraphQL с помощью Router Cosmo означает, что вы сможете масштабироваться горизонтально для обработки большого количества одновременных входящих запросов - обычное требование в корпоративных средах.
Полученная здесь предсказуемость критически важна для поддержания (и отладки) надежной системы. Кроме того, отсутствие состояния делает ваш единый API устойчивым. Если один размещенный экземпляр Router выйдет из строя, другой может бесшовно заменить его, поскольку нет состояния сессии, которое нужно сохранить.
Отсутствие состояния в Router - это задумано по дизайну, и оно обеспечивает масштабируемость и гибкость в архитектуре. Каждый из фактических сервисов (которые содержат функции разрешения для определенных типов и полей) отвечает за управление своим собственным состоянием. Это позволяет вам разрабатывать и масштабировать ваши сервисы независимо.
4. Настройка
Вы можете расширить функциональность Router Cosmo, создавая пользовательские модули, написанные на Go.
Чтобы создать пользовательский модуль, вам нужно реализовать один или несколько из этих предопределенных интерфейсов:
core.RouterMiddlewareHandler — пользовательская функция промежуточного обработчика, которая будет вызываться для каждого клиентского запроса, проходящего через Router. Например, вы можете создать промежуточное ПО, которое регистрирует входящие запросы и ответы GraphQL, добавляет или изменяет заголовки в запросе или ответе, кэширует результаты часто выполняемых запросов, проверяет входящие запросы GraphQL и отклоняет недопустимые запросы/мутации, прежде чем они достигнут движка GraphQL, и многое другое.
core.EnginePreOriginHandler — пользовательский обработчик, который запускается перед тем, как запрос будет перенаправлен на подграф, вызывается для каждого запроса подграфа. Вы можете использовать его для регистрации (для отладки/аудита) или манипуляции с заголовками (для пользовательской аутентификации, безопасности и т.д.)
core.EnginePostOriginHandler — пользовательский обработчик, который запускается после того, как запрос был отправлен на подграф, но до того, как ответ был передан движку GraphQL, вызывается для каждого ответа подграфа. Вы можете использовать это для кэширования ответа (в памяти или Redis), или перехватывать и обрабатывать ошибки, которые происходят в ответе подграфа — регистрация ошибок, или форматирование ответов об ошибках последовательно для клиента.
core.Provisioner — реализует хук жизненного цикла модуля, который выполняется при создании экземпляра модуля. Используйте его для подготовки вашего модуля и проверки конфигурации. Вы можете использовать этот хук для настройки внутреннего состояния, или выполнения любых предварительных задач инициализации, таких как выделение ресурсов (установление пула соединений с базой данных), или проверка того, что все необходимые значения конфигурации присутствуют и что они имеют допустимые форматы и значения (и вызывать исключения, если нет).
core.Cleaner — реализует хук жизненного цикла модуля, который выполняется после того, как сервер был остановлен. Обратный к core.Provisioner, он используется для освобождения ресурсов, корректного завершения соединений и выполнения любой другой необходимой очистки.
Этот подход предлагает уровень настраиваемости, аналогичный тому, что делает xcaddy для сервера Caddy, устраняя сложности, связанные с написанием скриптов Rhai для настройки предварительно скомпилированного двоичного файла, как это делается с Router Apollo.
Написание тестов для пользовательских модулей также чрезвычайно просто. Здесь представлен полностью протестированный пример.
5. Аналитика — OpenTelemetry, Prometheus и RED Metrics
Router Cosmo был инструментирован с помощью OpenTelemetry — открытого фреймворка для наблюдения, который может собирать, обрабатывать и экспортировать метрики, трассировки и логи из приложений и сервисов. Router отправляет метрики производительности в OTEL Collector, давая вам представление о пути, пройденном вашим API-трафиком на уровне каждого запроса через ваш федеративный граф, вместе с конкретной операцией, выполненной в запросе.
Вы даже можете объединить это с метриками OTEL, отправляемыми вашими подграфами, чтобы иметь полный контроль над оптимизацией вашей инфраструктуры. Вы можете прочитать больше о том, как инструментировать ваши отдельные сервисы/подграфы с помощью OTEL здесь.
Если вы используете платформу WunderGraph Cosmo "все в одном", вы можете визуализировать эти данные и видеть, как запрос проходит через разные сервисы во вкладке Аналитика вашей панели Cosmo.
Если вы используете только Router Cosmo, любой бэкенд, совместимый с протоколом OpenTelemetry (OTLP) — Jaeger, DataDog и т.д. — может импортировать метрики, сгенерированные Router Cosmo, что означает, что вы получаете централизованный мониторинг и анализ по всей вашей инфраструктуре, независимо от вашего стека мониторинга.
С Router Cosmo вы также получаете метрики Prometheus — проверенную временем, открытую (лицензия Apache) систему мониторинга сервисов — и метрики R.E.D, что означает, что у вас есть полный стек метрик для детального понимания трафика маршрутизатора, коэффициентов ошибок/успеха или средних времен запроса/ответа/размеров конкретных операций, и в общем, все, что вам нужно, чтобы определить узкие места и оптимизировать производительность вашей системы.
6. Пересылка заголовков клиента в подграфы
При работе с федеративной архитектурой GraphQL вам часто придется пересылать определенные заголовки клиента в подграфы. Это может быть необходимо, потому что вам нужно передать контекстную информацию, такую как стратегии кэширования, токены аутентификации, предпочтения пользователя или просто информацию, специфичную для устройства, чтобы ваши подграфы могли принимать решения на основе некоторого контекста клиента.
Router Cosmo упрощает пересылку заголовков HTTP в подграфы. По умолчанию ни один из них не пересылается по соображениям безопасности. Но вы можете изменить файл конфигурации Router-а (config.yaml
в рабочем каталоге Router), чтобы добавить корневой ключ заголовков, и настроить его с правилами заголовков Cosmo в соответствии с вашими потребностями. Эти правила применяются в том порядке, в котором они появляются в этом файле YAML.
config.yaml
headers:
all: # указывает, что эти правила заголовков применяются ко всем подграфам
request:
- op: "propagate" # правило заголовка Cosmo
named: X-Test-Header # точное совпадение
- op: "propagate"
matching: (?i)^X-Custom-.* # совпадение по regex (регистронезависимое)
Правило заголовка propagate
пересылает все соответствующие заголовки запроса клиента в подграфы.
Как только вы установите этот ключ в YAML-файл, именованный вложенный ключ будет использоваться для точного соответствия имени заголовка (помните, что с пакетом http Golang каждое слово, разделенное дефисом, пишется с заглавной буквы. т.е., x-test-header станет X-Test-Header, и это то, что вам нужно использовать), а вложенный ключ matching используется, когда вы хотите использовать Regex для сопоставления имени заголовка.
И, конечно, если вы хотите установить правило заголовка по умолчанию, которое применяется даже если клиент его не установил, вы можете сделать это.
config.yaml
headers:
all:
request:
- op: "propagate"
named: "X-User-Id"
default: "123" # установить значение вручную, если клиент не предоставил его явно
Тестирование и развертывание
Router Cosmo извлекает последнюю конфигурацию вашей федеративной архитектуры из платформы Cosmo или CDN. Для отладки и локальной разработки вы, возможно, захотите переопределить это поведение и загрузить конфигурацию из локального файла, например, так:
docker run \
-e ROUTER_CONFIG_PATH=/app/config.json \
-v ./config.json:/app/config.json \
- env-file ./.env router
Где config.json
- это ваш локальный тестовый файл конфигурации.
Если вы хотели бы работать локально с копией вашей производственной конфигурации, вы могли бы извлечь ее, прежде чем запускать команду выше.
npx wgc router fetch production > config.json
Где production
- это имя федеративного графа, конфигурацию маршрутизатора которого вы хотите получить.
Аналогично, если вы хотели бы сгенерировать конфигурацию маршрутизатора локально (без подключения к компоненту Control Plane платформы Cosmo) из ваших подграфов, перенаправьте это в файл и используйте это для команды docker выше,
npx wgc router compose -i config.json
Наконец, Router Cosmo - это приложение Go, предоставляемое в виде самодостаточного контейнера Docker. Вы можете запустить Router, выполнив одну команду Docker.
docker run \
- name cosmo-router \
-e FEDERATED_GRAPH_NAME=<federated_graph_name> \
-e GRAPH_API_TOKEN=<router_api_token> \
-e LISTEN_ADDR=0.0.0.0:3002 \
- platform=linux/amd64 \
-p 3002:3002 \
ghcr.io/wundergraph/cosmo/router:latest
? Вы можете сгенерировать новый GRAPH_API_TOKEN
для федеративного графа так:
npx wgc router token create mytoken --graph-name <name>
Поскольку маршрутизатор не имеет состояния, вы можете развернуть несколько экземпляров. WunderGraph Cosmo рекомендует 3 экземпляра, спецификации каждого экземпляра составляют 2 CPU с 1 ГБ оперативной памяти для проектов с низким-средним трафиком.
Куда двигаться дальше
Для большинства людей Router и Gateway от Apollo будут вполне подходить для архитектур федеративного GraphQL. Однако для корпоративных сценариев использования препятствием к внедрению является то, что эти два продукта находятся под лицензией Elastic V2, которую OSI не считает открытой. Кроме того, корпоративные сценарии использования часто имеют уникальные и строгие требования к соблюдению нормативов по данным, и проблема привязанности к поставщику является реальной.
Вот почему молниеносно быстрый, полностью открытый, совместимый с Federation V1/V2/Open Federation Router Cosmo (и Cosmo как альтернативная платформа Apollo GraphOS/Studio) имеет смысл в этих сценариях использования.
Чтобы узнать больше, вы можете ознакомиться с их документацией. Также вы можете найти WunderGraph Discord, если у вас есть вопросы или проблемы, которые вы хотите обсудить.