Пространство имен – это важный концепт в программировании, позволяющий группировать элементы и предотвращать конфликты имен. В этом посте мы покажем, как мы применяем этот концепт к API, чтобы облегчить композицию и интеграцию различных сервисов.
Мы покажем вам, как интегрировать 8 сервисов: SpaceX GraphQL, 4x GraphQL с использованием Apollo Federation, REST API с использованием OpenAPI Specification, API на основе PostgreSQL и API на основе Planetscale-Vitess (MySQL) всего несколькими строками кода, полностью автоматически, без каких-либо конфликтов.
Когда вы устанавливаете пакет npm
, он находится в своем собственном пространстве имен. Одним из таких пакетов является axios
, очень популярный клиент для выполнения HTTP-запросов.
Чтобы установить axios
, вы выполняете следующую команду:
yarn add axios
Это устанавливает зависимость axios
в вашу папку node_modules
и добавляет ее в ваш файл package.json
.
Теперь вы можете импортировать и использовать код, предоставленный пакетом axios
, следующим образом:
import axios from "axios"; const res = await axios.get("https://example.com");
Импортируйте зависимость, дайте ей имя, в данном случае просто axios
, затем используйте ее. Мы также могли бы переименовать axios
в bxios
. Переименование импорта – это важный элемент управления зависимостями, чтобы избежать конфликтов.
Одно из основных правил – у вас не должно быть двух импортов с одним и тем же именем, иначе у вас возникнет конфликт имен, и будет непонятно, как должна выполняться программа.
Должны ли мы запустить axios
или bxios
?
Хорошо, достаточно введения. Вы, вероятно, уже знакомы со всем этим, что это имеет отношение к API?
Многое! По крайней мере, я так думаю. Весь этот рабочий процесс потрясающий!
Вы можете написать код, упаковать его в виде пакета npm
, опубликовать его, и другие смогут импортировать и использовать его очень легко. Это такой приятный способ сотрудничать с помощью кода.
Как это выглядит при использовании API? Ну, это не такая смазанная машина. С API мы все еще в каменном веке, когда речь идет об этом рабочем процессе.
Некоторые компании предлагают SDK, который вы можете скачать и интегрировать. Другие просто публикуют REST или GraphQL API. У некоторых из них есть спецификация OpenAPI, другие просто предлагают свою собственную пользовательскую документацию по API.
Представьте, что вам пришлось бы интегрировать 8 сервисов, чтобы получить данные от них. Почему бы вам просто не запустить что-то похожее на yarn add axios
и выполнить работу? Почему так сложно объединить сервисы?
Проблема – как объединить API без конфликтов
Чтобы добиться этого, нам нужно решить ряд проблем.
-
Нам нужно прийти к общему языку, универсальному языку, чтобы объединить все наши API
-
Нам нужно найти способ “пространственно” разделить наши API, чтобы разрешить конфликты
-
Нам нужна среда выполнения для выполнения “пространственно разделенных” операций
Давайте рассмотрим проблемы по очереди.
GraphQL: универсальный язык интеграции API
Первая проблема, которую нужно решить, – это нам нужен общий язык, на котором будет основываться наш подход к реализации. Не отвлекаясь на сторонние темы, позвольте мне объяснить, почему GraphQL отлично подходит для этой цели.
GraphQL обладает двумя очень мощными функциями, которые необходимы для нашего случая использования. С одной стороны, он позволяет нам запрашивать именно те данные, которые нам нужны. Это очень важно, когда мы используем много источников данных, так как мы можем легко проникнуть в поля, которые нас интересуют.
С другой стороны, GraphQL позволяет нам легко строить и следовать за ссылками между типами. Например, у вас могут быть две конечных точки REST, одна с сообщениями, другая с комментариями. С GraphQL API перед ними, вы можете построить ссылку между двумя объектами и позволить вашим пользователям получать сообщения и комментарии с помощью одного запроса.
Кроме того, у GraphQL есть процветающее сообщество, много конференций и людей, активно участвующих, создающих инструменты вокруг языка запросов и многое другое.
GraphQL и микросервисы: Сшивка схемы против Федерации
Стоит отметить, что у GraphQL также есть слабое место, когда речь идет об интеграции API. У него нет концепции пространств имен, что делает его использование для интеграции API немного сложным, до сих пор!
Когда речь идет об интеграции сервисов, на данный момент существуют два основных подхода к решению проблемы. Во-первых, это Сшивка схемы (Schema Stitching), а затем – Федерация.
С помощью Сшивки схемы вы можете объединять сервисы GraphQL, которые не знают о сшивке. Объединение API происходит в централизованном месте, шлюзе API GraphQL, без ведома сервисов.
Федерация, определенная Apollo, с другой стороны, предлагает другой подход. Вместо централизации логики и правил сшивки, федерация распределяет их по всем микросервисам GraphQL, также известным как Подграфы. Каждый Подграф определяет, как он вносит свой вклад в общую схему, полностью осознавая, что существуют другие Подграфы.
Здесь нет “лучшего” решения. Оба подхода хороши для микросервисов. Они просто разные. Один предпочитает централизованную логику, в то время как другой предлагает децентрализованный подход. Оба подхода имеют свои собственные проблемы.
Тем не менее, проблема интеграции сервисов выходит далеко за рамки федерации и сшивки схемы.
Один граф для всех, или нет!
Основной шаблон Принципиального GraphQL касается целостности и гласит:
Ваша компания должна иметь один объединенный граф, а не несколько графов, созданных каждой командой. Имея один граф, вы максимизируете значение GraphQL:
Больше данных и сервисов можно получить из одного запроса
Код, запросы, навыки и опыт переносимы между командами
Один центральный каталог всех доступных данных, на который могут смотреть все пользователи графа
Стоимость реализации минимизируется, потому что работа по реализации графа не дублируется
Централизованное управление графом – например, единые политики контроля доступа – становится возможным
Когда команды создают свои собственные индивидуальные графы без координации своей работы, это неизбежно приводит к тому, что их графы начинают перекрываться, добавляя одни и те же данные в граф несовместимыми способами. В лучшем случае, это обходится дорого; в худшем случае, это создает хаос. Этому принципу требуется следовать как можно раньше на пути принятия графа компанией.
Давайте сравним этот принцип с тем, что мы узнали о коде выше, вы знаете, пример с axios
и bxios
.
Больше данных и сервисов можно получить из одного запроса
Представьте, что был один гигантский пакет npm
на компанию со всеми зависимостями. Если бы вы хотели добавить axios
в свой пакет npm
, вам пришлось бы вручную скопировать весь код в свою собственную библиотеку и сделать его “своим” пакетом. Это было бы не поддерживаемо.
Один единый граф звучит здорово, когда вы находитесь в полной изоляции. На самом деле, однако, это означает, что вы должны добавить все внешние API, все “пакеты”, которые вы не контролируете, в свой один граф. Эту интеграцию должны поддерживать вы сами.
Код, запросы, навыки и опыт переносимы между командами
Это правда. С помощью всего одного графа мы можем легко делиться запросами между командами. Но является ли это действительно функцией? Если мы разделим наш код на пакеты и опубликуем их отдельно, другим будет легко выбрать именно то, что им нужно.
Представьте себе один граф с миллионами полей. Это действительно масштабируемое решение? Как насчет просто выбора подчастей гигантской схемы GraphQL, которые действительно важны для вас?
Один центральный каталог всех доступных данных, на который могут смотреть все пользователи графа
С помощью всего одной схемы у нас может быть централизованный каталог, это правда. Но помните, что этот каталог может представлять только наш собственный API. А как насчет всех других API в мире?
Кроме того, почему мы не можем иметь каталог из нескольких API? Просто как пакеты npm
, которые вы можете искать и просматривать.
Стоимость реализации минимизируется, потому что работа по реализации графа не дублируется
Я бы поспорил, что на самом деле обратное верно. Особенно с Федерацией, предложенным Apollo решением для реализации графа, становится гораздо сложнее поддерживать ваш граф. Если вы хотите объявить устаревшими определения типов в нескольких подграфах, вам придется тщательно координировать изменение во всех из них.
Микросервисы не действительно микро, если между ними есть зависимости. Этот шаблон скорее называется распределенный монолит.
Централизованное управление графом – например, единые политики контроля доступа – становится возможным
Интересно, что должно быть возможно, но не является реальностью. Мы еще не видели централизованной системы политик контроля доступа, которая добавляет контроль доступа на основе ролей для федеративных графов. О, на самом деле это одна из наших функций, но давайте сегодня не будем говорить о безопасности.
Почему принцип “Один граф” не имеет смысла
Построение одного единого графа звучит как отличная идея, когда вы находитесь в полной изоляции на маленьком острове без интернета. Вероятно, вы не собираетесь использовать и интегрировать сторонние API.
Любой другой, кто подключен к интернету, вероятно, захочет интегрировать внешние API. Хотите проверить продажи с помощью API Stripe? Отправлять электронные письма через Mailchimp или Sendgrid? Действительно ли вы хотите вручную добавлять эти внешние сервисы в свой “Один граф”?
Принцип “Один граф” не проходит проверку реальностью. Вместо этого нам нужен простой способ составить несколько графов!
Мир разнообразен. Многие отличные компании предлагают действительно хорошие продукты через API. Давайте упростим построение интеграций, не прибегая к ручному добавлению их в наш “Один граф”.
Пространства имен GraphQL: Слияние любого количества API без конфликтов
Это приводит нас ко второй проблеме, конфликтам имен.
Представьте, что и Stripe, и Mailchimp определяют тип Customer, но у каждого из них свое понимание Customer, с разными полями и типами.
Как оба типа Customers могут сосуществовать в одной и той же схеме GraphQL? Как предложено выше, мы берем на вооружение концепцию из языков программирования, пространства имен!
Как это достичь? Давайте немного разберем эту проблему. Поскольку у GraphQL нет встроенной функции пространств имен, нам придется быть немного креативными.
Во-первых, нам нужно устранить все конфликты имен для типов. Это можно сделать, добавив суффикс к каждому типу “Customer” с пространством имен. Так, у нас были бы “Customer_stripe” и “Customer_mailchimp”. Первая проблема решена!
Еще одной проблемой, с которой мы могли бы столкнуться, являются конфликты имен полей в корневых типах операций, то есть в типах Query, Mutation и Subscription. Мы можем решить эту проблему, добавив префикс ко всем полям, например, stripe_customer(by: ID!)
и mailchimp_customer(by: ID!)
.
Наконец, нам нужно быть осторожными с другой функцией GraphQL, часто игнорируемой другими подходами к этой проблеме, Directives!
Что произойдет, если вы определите директиву под названием @formatDateString
и две схемы, но у них разное значение? Не приведет ли это к непредсказуемым путям выполнения? Да, вероятно. Давайте также исправим это.
Мы можем переименовать директиву в @stripe_formatDateString
и @mailchimp_formatDateString
соответственно. Таким образом, мы можем легко различать две директивы.
С этим все конфликты имен должны быть решены. Мы уже закончили? На самом деле нет. К сожалению, с нашим решением мы создали много новых проблем!
WunderGraph: Среда выполнения для облегчения пространств имен GraphQL
Переименовав все типы и поля, мы на самом деле создали много проблем. Давайте посмотрим на этот запрос:
{ mailchimp_customer(by: ID!) { id name registered @mailchimp_formatDateString(format: "ddmmYYYY") ... on PaidCustomer_mailchimp { pricePlan } } }
В чем здесь проблемы?
Поле mailchimp_customer
не существует в схеме Mailchimp, мы должны переименовать его в customer
.
Директива mailchimp_formatDateString
также не существует в схеме Mailchimp. Мы должны переименовать ее в formatDateString
перед отправкой в апстрим. Но будьте осторожны с этим! Убедитесь, что эта директива действительно существует на источнике. Мы автоматически проверяем это, так как вы можете случайно использовать неправильную директиву на неправильном поле.
Наконец, определение типа PaidCustomer_mailchimp
также не существует в исходной схеме. Мы должны переименовать его в PaidCustomer
, иначе источник его не поймет.
Звучит как много работы? На самом деле, это уже сделано, и вы можете использовать это прямо сейчас. Просто введите yarn global add @wundergraph/wunderctl
в своем терминале, и вы готовы его опробовать! (*deprecated, утилиту переписали с TypeScript на GoLang)
С этим мы готовы к фазе реализации.
Импорт зависимостей API
На первом шаге нам нужно “импортировать” наши зависимости API. Мы можем сделать это с помощью SDK WunderGraph. Просто “интроспектируйте” все различные сервисы и объедините их в “приложение”.
const federated = introspect.federation({ apiNamespace: "federation", upstreams: [ {url: "http://localhost:4001/graphql"}, {url: "http://localhost:4002/graphql"}, {url: "http://localhost:4003/graphql"}, {url: "http://localhost:4004/graphql"}, ] }) const planetscale = introspect.planetscale({ apiNamespace: "planetscale", databaseURL: `mysql://${planetscaleCredentials}@fwsbiox1njhc.eu-west-3.psdb.cloud/test?sslaccept=strict`, }) const spaceX = introspect.graphql({ apiNamespace: "spacex", url: "https://api.spacex.land/graphql/", }); const postgres = introspect.postgresql({ apiNamespace: "postgres", databaseURL: "postgresql://admin:admin@localhost:54322/example?schema=public", }); const jsonPlaceholder = introspect.openApi({ apiNamespace: "jsp", source: { kind: "file", filePath: "jsonplaceholder.yaml" } }) configureWunderGraphApplication({ apis: [ postgres, spaceX, jsonPlaceholder, planetscale, federated ], });
Если вы посмотрите на код, вы, вероятно, заметите ключевое слово apiNamespace
несколько раз. apiNamespace
гарантирует, что каждый API находится в своей собственной границе. Таким образом, автоматически избегаются конфликты имен.
Как только вы проинтроспектировали все зависимости, мы готовы написать запрос, который охватывает все 8 сервисов.
Мы хотим получить пользователей из API SpaceX, пользователей из API JSON Placeholder, больше пользователей из нашей базы данных PostgreSQL, еще больше пользователей из базы данных Planetsacle и, наконец, одного пользователя с отзывами и продуктами из федеративного графа.
Все это возможно благодаря нашему богатому набору источников данных.
{ spacexUsers: spacex_users { id name } jspUsers: jsp_users { id name posts { id title comments { id body } } } postgresUsers: postgres_findManyusers { id email } planetscaleUsers: planetscale_findManyusers { id first_name last_name email } federation: federation_me { id name reviews { id body product { upc name } } } }
Обратите внимание, как он использует префиксированные/пространственные корневые поля. Этот запрос дает нам данные от всех 8 сервисов сразу, это сумасшествие!
Теперь запустите wunderctl up
, чтобы все это заработало за пару секунд. Это работает на вашем локальном компьютере без вызова каких-либо облачных сервисов.
Итого
В этом посте мы начали с обсуждения того, как пространства имен облегчают написание и обмен кодом. Затем мы исследовали различия между “подходом к кодированию” и необходимостью иметь дело с интеграцией API.
Мы изучили Сшивку схем и Федерацию и узнали, что оба подхода хороши, но этого недостаточно. Мы рассмотрели принцип “Один граф” и поняли, что у него есть свои недостатки.
Наконец, мы представили концепцию пространств имен GraphQL API, что позволяет объединять GraphQL, Federation, REST, PostgreSQL и Planetscale API всего в несколько строк кода.
Если вы заинтересованы в том, чтобы увидеть все это в действии, здесь есть видео, где я прохожу через весь процесс: https://youtu.be/jUaJkvPmCSQ
Наша цель для WunderGraph – стать “Менеджером пакетов для API”. Мы еще не совсем там, но в конечном итоге вы сможете запустить wunderctl integrate stripe/stripe
, затем написать Запрос или Мутацию, и интеграция будет выполнена.