Источник: Хабр "GraphQL и микросервисная архитектура: объединяем сервисы в федерацию"

Меня зовут Владислав Гончаров, я разработчик в команде Platform V DataSpace СберТеха. Расскажу, как мы решаем вопрос с объединением сервисов в GraphQL и микросервисной архитектуре, которая позволяет разбить любое большое приложение на маленькие сервисы. С одной стороны, их проще написать и поддерживать небольшой командой. А с другой — некоторые задачи теперь требуют выполнения сразу нескольких запросов вместо одного.
Например, возьмём банковское приложение. Его можно разбить на сервисы:
- «Клиенты» — сервис, хранящий данные о клиентах;
- «Счета» — сервис, хранящий данные о счетах (при сохранении этой информации потребуется идентификатор клиента);
- «Кредиты» — сервис, хранящий данные о кредитах (при сохранении этой информации потребуется идентификатор счёта), и др.
Предположим, что нам нужно получить информацию о кредите (сумма займа и ФИО заёмщика) по номеру кредитного договора. В микросервисной архитектуре алгоритм будет такой:
- Через сервис «Кредиты» найти кредит по номеру кредитного договора и получить сумму займа и идентификатор счёта.
- Через сервис «Счета» найти счёт по его идентификатору и получить идентификатор владельца (клиента).
- Через сервис «Клиенты» найти клиента по его идентификатору и получить ФИО.
Распишем этот алгоритм в виде GraphQL‑запросов.
Поиск кредита по номеру кредитного договора («АА №000001») и получение суммы займа и идентификатора счёта
Запрос:
query { searchCredit(cond: "it.contractNumber == 'AА №000001'") { elems { loanAmount account { entityId } } } }
Ответ:
{ "data": { "searchCredit": { "elems": [ { "loanAmount": 1000000, "account": { "entityId": "7088959748502519809" } } ] } } }
Поиск счёта по идентификатору («7088959748502519809») и получение идентификатора владельца (клиента)
Запрос:
query { searchAccount(cond: "it.$id == '7088959748502519809'") { elems { owner { entityId } } } }
Ответ:
{ "data": { "searchAccount": { "elems": [ { "owner": { "entityId": "7088959395241394177" } } ] } } }
Поиск клиента по идентификатору ("7088959395241394177") и получение ФИО
Запрос:
query { searchClient(cond: "it.$id == '7088959395241394177'") { elems { lastName firstName patronymic } } }
Ответ:
{ "data": { "searchClient": { "elems": [ { "lastName": "Иванов", "firstName": "Иван", "patronymic": "Иванович" } ] } } }
Для решения задачи нужно сделать несколько запросов к различным сервисам. Можно также отметить однообразные действия при поиске сущностей по идентификаторам из ссылок, которые неплохо было бы упростить. Давайте воспользуемся Apollo Federation и Schema Stitching.
Apollo Federation — это open source-решение, которое позволяет реализовать в GraphQL микросервисный подход. Вместе со Schema Stitching, Apollo Federation позволяет сделать сервис, который имеет в качестве GraphQL-схемы объединение GraphQL-схем нескольких сервисов. Благодаря этому ссылочные поля, которые были просто идентификаторами на GraphQL-схеме отдельного сервиса, становятся полноценными ссылками.
Вот что получилось в результате — рассмотрим на примере задачи по поиску кредита.
Поиск кредита по номеру кредитного договора ("АА №000001") и получения суммы займа и ФИО заёмщика
Запрос:
query { searchCredit(cond: "it.contractNumber == 'AА №000001'") { elems { loanAmount account { entity { owner { entity { lastName firstName patronymic } } } } } } }
Ответ:
{ "data": { "searchCredit": { "elems": [ { "loanAmount": 1000000, "account": { "entity": { "owner": { "entity": { "lastName": "Иванов", "firstName": "Иван", "patronymic": "Иванович" } } } } } ] } } }
Объединение сервисов в DataSpace
Вот как на практике выглядит реализация сервиса с объединённой GraphQL-схемой сервисов DataSpace.
Описание сервисов
В первую очередь описываем модели наших сервисов:



Выпуск сервиса с объединённой GraphQL-схемой сервисов «Клиенты», «Счета» и «Кредиты»
Сервис с объединённой GraphQL‑схемой выпускается в рамках одного из объединяемых сервисов. В качестве такового выберем сервис «Кредиты». Подключим к нему внешние модели (сервисов «Счета» и «Клиенты»):





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

Примечание 3. На момент выпуска сервиса должны быть выпущены сервисы подключённых внешних моделей .
Теперь мы можем перейти на вкладку «Детали» и, помимо endPoint'ов сервиса «Кредиты», увидим endPoint сервиса с объединённой GraphQL‑схемой.

А на вкладке «GraphQL‑конструктор» можно выполнить GraphQL‑запрос для сервиса с объединённой GraphQL‑схемой (поставив соответствующую галочку напротив «GraphQL Stitching»).

Примечание
Стоит добавить, что GraphQL сам по себе не поддерживает наследование сущностей, но в DataSpace мы добавили такую возможность. Например, инструмент позволяет запросить сущность или уточнить у дочерних сущностей дополнительные поля.
Точно так же поступили с поиском: в GraphQL для этого нет инструментов, максимум — поиск по ID или нестандартизованная пагинация. В DataSpace для этого реализован целый набор инструментов, доступный на уровне GraphQL и позволяющий осуществлять пагинацию, сортировку и группировку. О том, как и зачем это было сделано, расскажем в следующем материале. Спасибо за внимание!