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

16125d32c78db5c9743a261ab8882570.png

Меня зовут Владислав Гончаров, я разработчик в команде Platform V DataSpace СберТеха. Расскажу, как мы решаем вопрос с объединением сервисов в GraphQL и микросервисной архитектуре, которая позволяет разбить любое большое приложение на маленькие сервисы. С одной стороны, их проще написать и поддерживать небольшой командой. А с другой — некоторые задачи теперь требуют выполнения сразу нескольких запросов вместо одного.

Например, возьмём банковское приложение. Его можно разбить на сервисы:

  • «Клиенты» — сервис, хранящий данные о клиентах;

  • «Счета» — сервис, хранящий данные о счетах (при сохранении этой информации потребуется идентификатор клиента);

  • «Кредиты» — сервис, хранящий данные о кредитах (при сохранении этой информации потребуется идентификатор счёта), и др.

Предположим, что нам нужно получить информацию о кредите (сумма займа и ФИО заёмщика) по номеру кредитного договора. В микросервисной архитектуре алгоритм будет такой:

  1. Через сервис «Кредиты» найти кредит по номеру кредитного договора и получить сумму займа и идентификатор счёта.

  2. Через сервис «Счета» найти счёт по его идентификатору и получить идентификатор владельца (клиента).

  3. Через сервис «Клиенты» найти клиента по его идентификатору и получить ФИО.

Распишем этот алгоритм в виде 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. Имя модели можно посмотреть в параметрах модели.

193a8627d3a9d4f8ff85df0968ee0639.png

Примечание 3. На момент выпуска сервиса должны быть выпущены сервисы подключённых внешних моделей .

Теперь мы можем перейти на вкладку «Детали» и, помимо endPoint’ов сервиса «Кредиты», увидим endPoint сервиса с объединённой GraphQL‑схемой.

452d9eff5f8fb4524be9f4b39a40e258.png

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

06284b7a9ca8c62c6c19b82f74b885ae.png

Примечание

Стоит добавить, что GraphQL сам по себе не поддерживает наследование сущностей, но в DataSpace мы добавили такую возможность. Например, инструмент позволяет запросить сущность или уточнить у дочерних сущностей дополнительные поля.

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

© Habrahabr.ru