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 и позволяющий осуществлять пагинацию, сортировку и группировку. О том, как и зачем это было сделано, расскажем в следующем материале. Спасибо за внимание!