Предприятия обладают различными зависимостями по данным: внутренними микросервисами со специализированными доменами данных, унаследованными системами с проприетарными форматами данных, а также сторонними API и SaaS-приложениями с уникальными моделями данных и конечными точками.

Это различные (и часто унаследованные) технологии, которые необходимо как-то объединить.

Federated GraphQL (Федеративный GraphQL) стал основным решением для такого расклада в корпоративной сфере. Router (маршрутизатор), или Gateway (шлюз), в Federation (Федерации) выступает в качестве стержня, связывающего все эти разрозненные источники данных воедино, делая их доступными через единый API и обеспечивая при этом адаптивность. Это ключевой фактор, благодаря которому Federated GraphQL позволяет создавать масштабируемые и модульные архитектуры.

Рассмотрим высокопроизводительный маршрутизатор от WunderGraph Cosmo с открытым исходным кодом, совместимый с Federation V1/V2. Мы расскажем о том, что он делает, почему так важен для стека Cosmo, как его разместить и даже настроить и расширить с помощью кода на языке Go.

WunderGraph Cosmo: общие сведения

WunderGraph Cosmo  —  это платформа с открытым исходным кодом (с лицензией Apache 2.0) для построения федеративных масштабируемых графов, управления ими и организации их совместной работы. Она представляет собой комплексное решение, заменяющее Apollo GraphOS/Studio и содержащее реестр схем, позволяющий проверять изменения и ошибки компоновки, невероятно быстрый маршрутизатор (Router), совместимый с Federation V1/V2, и платформу аналитики/распределенной трассировки для федеративного GraphQL.

Стек Cosmo отличается собственной концепцией и оптимизирован для достижения максимальной эффективности и производительности. Вот что он включает.

  • Studio (веб-интерфейс GUI) и wgc (инструмент командной строки) для управления платформой в целом  —  схемами, пользователями, проектами  —  и доступа к аналитике/трассировкам.
  • Router  —  компонент, который фактически реализует технологию GraphQL Federation, маршрутизацию запросов и агрегирование ответов.
  • Control Plane (панель управления)  —  “сердце” платформы, предоставляющее основные API, которые используются как Studio/CLI, так и Router.

Относительно хостинга: вы можно запустить всю платформу локально на любом сервисе Kubernetes (AWS, Azure, Google для этапа продакшена и Minikube для локальной разработки) или использовать управляемое облако Cosmo Cloud для всех Stateful-компонентов, а Stateless-маршрутизатор разместить самостоятельно.

Cosmo Router

Cosmo Router (который можно найти в монорепозитории Cosmo)  —  это альтернатива Apollo Gateway (или Apollo Router) с открытым исходным кодом (с лицензией Apache 2.0). Это HTTP-сервер, написанный на языке Golang, который является центральной точкой входа для федеративных GraphQL-архитектур и отвечает за маршрутизацию запросов к нужным микросервисам/подграфам, агрегирование ответов и отправку их обратно клиенту в едином формате. Вы даже можете настроить его, используя собственный код на чистом Go.

Router совместим как с Apollo Federation V1 и V2, так и с Open Federation  —  инициативой сообщества с открытым исходным кодом, направленной на создание и поддержание единой спецификации для федеративного GraphQL.

1. Как работает Cosmo Router

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

  1. Прежде всего ему необходим доступ к схеме федеративного графа, которая включает типы, поля и метаданные каждой схемы подграфа, а также их конечные точки. Все это периодически извлекается из CDN (сети доставки контента) высокой доступности, чтобы Router располагал самой свежей конфигурацией.
  2. На основе этой конфигурации формируется оптимизированный Query Plan (план запросов), который кэшируется между запросами для минимизации трудозатрат и определяет, как лучше разложить запрос клиента на несколько подзапросов, какие подзапросы могут выполняться параллельно, а какие  —  последовательно, и как объединить их ответы.
  3. Затем Router использует Query Plan для разложения на несколько подзапросов и выполняет их. Каждый подзапрос относится к микросервису/подграфу, который может выполнить часть общего запроса.
  4. Router собирает частичные результаты каждого из подзапросов, комбинирует их и собирает в целостный ответ, соответствующий структуре исходного запроса клиента, затем отправляет этот собранный ответ обратно клиенту.

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

2. Производительность

Cosmo Router работает на основе graphql-go-tools, высокоразвитого и оптимизированного движка GraphQL (лицензия MIT), который является самой быстрой и надежной реализацией для Federation V1. При этом Cosmo Router располагает собственными оптимизациями.

Кроме того, Go сам по себе ориентирован на производительность: он занимает мало места в памяти, имеет высокую скорость выполнения и компилируется в машинный код напрямую (в отличие от того же NodeJS, который зависит от движка JavaScript V8 и чреват определенными накладными расходами).

Рассмотрим два способа, с помощью которых Cosmo Router получает значительный прирост производительности.

Пакетная обработка

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

query {
allUsers {
id
name
posts {
id
title
content
}
}
}

Почему это неэффективно? А сколько раз вы обращаетесь к источникам данных?

  • Вы получаете всех Users (пользователей) одним запросом (это 1 запрос к сервису Users).
  • Для каждого пользователя делается отдельный запрос для получения его сообщений (это еще N запросов к сервису Posts, где N  —  количество пользователей).

Таким образом, если у вас 100 пользователей, то будет сделано 1 + 100 = 101 обращение. Это и есть пресловутая проблема N+1. В GraphQL она возложена на сервер, а не на клиент, хотя существует независимо от этого и может замедлить получение данных по мере роста числа пользователей или при наличии дополнительных вложенных данных.

В Cosmo Router для решения этой проблемы используется адаптированный паттерн DataLoader (вот ознакомительное видео). Вместо того чтобы выполнять отдельные запросы для каждого пользователя, Router анализирует клиентский запрос перед выполнением, чтобы определить, какие ключи соединения потребуются для разрешения отношений удаленных сообщений (Posts), и модифицирует первый подзапрос (получая всех пользователей), чтобы также извлечь их  —  в данном случае ключами соединения будут 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 запрос).
  • Однако теперь Cosmo Router объединяет получение сообщений для всех 10 пользователей в один запрос (это еще 1 запрос).

Таким образом, для получения всех необходимых данных теперь достаточно сделать 1 + 1 = 2 запроса независимо от количества пользователей. Значительное улучшение по сравнению с наивным подходом N+1.

DataLoader в Cosmo  —  это специализированная реализация, которая может еще больше. В серверах GraphQL стандартным поведением является разрешение полей в глубину  —  как при просмотре списка элементов по одному и доскональном разрешении каждого элемента перед переходом к следующему. Вместо этого паттерн DataLoader 3.0 от Cosmo разделяет традиционный процесс разрешения на два отдельных этапа.

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

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

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

Режим Ludicrous

Когда несколько одинаковых запросов (запрашивающих одни и те же данные) одновременно находятся “в движении” (обрабатываются одновременно), Cosmo Router посылает только один запрос на исходный сервер, а результат разделяет со всеми остальными активными запросами.

В контексте Cosmo этот режим определяется как “Ludicrous” (абсурдный) для Router: его включение устранит избыточные запросы одних и тех же данных, оптимизируя сетевой трафик и снижая нагрузку на исходный сервер.

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

3. “Stateless” (отсутствие состояния Router)

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

Полученная предсказуемость очень важна для поддержки (и отладки) надежной системы. Кроме того, из-за отсутствия состояния унифицированный API остается устойчивым к внешним воздействиям. Если один экземпляр Router выходит из строя, другой может без проблем заменить его, поскольку нет необходимости сохранять состояние сессии.

Отсутствие состояния Router заложено в его конструкции и обеспечивает масштабируемость и гибкость архитектуры. Каждый из сервисов (содержащий функции разрешителя для определенных типов и полей) отвечает за управление своим состоянием. Это позволяет разрабатывать и масштабировать сервисы независимо друг от друга.

4. Настройка

Функциональность Cosmo Router можно расширить путем создания пользовательских модулей, написанных на языке Go.

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

  1. core.RouterMiddlewareHandler  —  пользовательская функция промежуточного ПО, которая будет вызываться для каждого запроса клиента, проходящего через Router. Например, можно создать промежуточное ПО, которое будет регистрировать входящие GraphQL-запросы и ответы, добавлять или изменять заголовки в запросе или ответе, кэшировать результаты часто выполняемых запросов, проверять входящие GraphQL-запросы и отклонять некорректные запросы/изменения до того, как они достигнут GraphQL Engine, и т. д.
  2. core.EnginePreOriginHandler  —  пользовательский обработчик, который запускается перед передачей запроса в подграф и вызывается для каждого запроса к подграфу. Его можно использовать для логгинга (для отладки/аудита) или для манипулирования заголовками (пользовательская авторизация, безопасность и т. д.).
  3. core.EnginePostOriginHandler  —  пользовательский обработчик, запускаемый после отправки запроса в подграф, но до передачи ответа GraphQL Engine, вызываемый для каждого ответа подграфа. Его можно использовать для кэширования ответа (in-memory или Redis), а также для перехвата и обработки ошибок, возникающих в ответе подграфа,  —  логгинга ошибок, или последовательного форматирования ответов на ошибки для клиента.
  4. core.Provisioner реализует хук жизненного цикла модуля, который выполняется при инстанцировании модуля. Используется для подготовки модуля и валидации его конфигурации. С помощью этого хука можно настраивать внутреннее состояние, а также выполнять любые предварительные задачи, такие как распределение ресурсов (создание пула соединений с базой данных) или проверка наличия всех необходимых конфигурационных значений и их корректных форматов и значений (в противном случае вызывать исключения).
  5. core.Cleaner реализует хук жизненного цикла модуля, который выполняется после выключения сервера. Являясь аналогом core.Provisioner, он используется для деаллокации ресурсов, изящного завершения соединений и выполнения любой другой необходимой очистки.

Такой подход обеспечивает уровень кастомизации Cosmo Router, аналогичный тому, который предлагает xcaddy для сервера Caddy. При этом устраняются сложности, связанные с написанием Rhai-скриптов для настройки предварительно скомпилированного бинарного файла, как в случае с Apollo Router.

5. Аналитика: метрики OpenTelemetry, Prometheus и RED

Cosmo Router оснащен инструментарием OpenTelemetry (OTEL)  —  фреймворком для мониторинга с открытым исходным кодом, который позволяет собирать, обрабатывать и экспортировать метрики, трассировки и логи приложений и сервисов. Router передает метрики производительности на OTEL-коллектор, что позволяет получить комплексное представление о пути, пройденном трафиком API на уровне каждого запроса через федеративный граф, а также о конкретной операции, выполненной в запросе.

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

Если вы используете комплексную платформу WunderGraph Cosmo, вы можете визуализировать эти данные и на вкладке “Analytics” дашборда Cosmo увидеть, как запрос проходит через различные сервисы.

Если вы используете только Cosmo Router, то любой бэкенд, совместимый с OpenTelemetry Protocol (OTLP), например Jaeger или DataDog, может импортировать метрики, генерируемые Cosmo Router. Это обеспечивает централизованный мониторинг и анализ всей инфраструктуры, независимо от используемого стека мониторинга.

С Cosmo Router вы также получаете метрики Prometheus  —  проверенной на практике системы мониторинга сервисов с открытым исходным кодом (лицензия Apache)  —  и метрики RED (Rate  —  запросы в секунду; Errors  —  ошибки в секунду; Duration  —  время на каждый запрос). Таким образом, у вас есть полный стек метрик для тонкого анализа трафика Router, частоты ошибок/успехов или среднего времени запросов/ответов/размеров конкретных операций и вообще всего, что нужно для определения узких мест и оптимизации производительности системы.

6. Направление клиентских заголовков в подграфы

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

Cosmo Router позволяет легко перенаправлять HTTP-заголовки к подграфам. В целях безопасности они не передаются по умолчанию. Но вы можете изменить конфигурационный файл Router (config.yaml в рабочем каталоге Router), добавив корневой ключ headers, и настроить его согласно правилам заголовков Cosmo в соответствии с вашими потребностями. Эти правила применяются в порядке их появления в данном YAML-файле.

# config.yaml
headers:
all: # указывает на то, что эти правила заголовков применяются ко всем подграфам
request:
- op: "propagate" # Правило заголовков Cosmo
named: X-Test-Header # точное совпадение

- op: "propagate"
matching: (?i)^X-Custom-.* # совпадение regex (нечувствительно к регистру)

Правило “propagate” (распространения заголовков) перенаправляет все совпадающие заголовки клиентских запросов в подграфы.

После установления корневого ключа headers в YAML-файле, именованный вложенный ключ используется для точного соответствия имени заголовка (помните, что в пакете Golang http каждое слово, разделенное дефисом, пишется с большой буквы, т. е. x-test-header станет X-Test-Header, и именно его вы должны использовать), а соответствующий вложенный ключ используется, когда нужно применить регулярное выражение для соответствия имени заголовка.

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

# config.yaml
headers:
all:
request:
- op: "propagate"
named: "X-User-Id"
default: "123" # установка значения вручную, если клиент его явно не предоставил

Тестирование и развертывание

Cosmo Router получает последнюю конфигурацию федеративной архитектуры с платформы 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  —  имя объединенного графа, конфигурацию маршрутизатора которого нужно получить.

Аналогично, если вам понадобится сгенерировать конфигурацию Router локально (без подключения к компоненту Control Plane платформы Cosmo) из подграфов, передайте это в файл и используйте вместо приведенной выше docker-команды:

  • npx wgc router compose -i config.json

В конце концов, Cosmo Router  —  это приложение на языке Go, поставляемое в виде автономного контейнера Docker. Запустить маршрутизатор можно, выполнив единственную 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 federated-graph create-token <name>

Поскольку Router не имеет состояний, можно развернуть несколько экземпляров. WunderGraph Cosmo рекомендует использовать 3 экземпляра, при этом для проектов с низким и средним трафиком рекомендуется использовать 2 процессора и 1 ГБ оперативной памяти.

Заключение

Большинство пользователей вполне могут применять Apollo Router и Gateway для федеративных архитектур GraphQL. Однако корпоративные пользователи рискуют столкнуться с непреодолимыми препятствиями на этапе внедрения, поскольку эти два продукта выпускаются под лицензией Elastic V2, которая является ограничительной и не рассматривается OSI (Open Systems Interconnection, модель взаимосвязи открытых систем) как лицензия на продукты с открытым исходным кодом. Кроме того, корпоративные решения часто отличаются уникальными и строгими требованиями к соответствию данных, и зависимость от поставщика может стать настоящей проблемой.

Вот почему быстрый, совместимый с Federation V1/V2/Open Federation и полностью открытый Cosmo Router (и Cosmo как альтернативная платформа Apollo GraphOS/Studio)  —  идеально подходит для таких случаев.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Prithwish Nath: An Introduction to WunderGraph Cosmo Router

Предыдущая статьяТехнология составления промптов для модели ИИ на примере одного чат-бота 
Следующая статьяКак автоанализ кода с помощью ИИ повышает безопасность приложений