Перейти к основному содержимому

Карта возврата из GraphQL

· 5 мин. чтения

1. Обзор

На протяжении многих лет GraphQL был широко принят в качестве одного из шаблонов коммуникации для веб-сервисов. Несмотря на то, что он богат и гибок в использовании, в некоторых сценариях он может создавать проблемы. Один из них — вернуть карту из запроса, что является проблемой, поскольку карта не является типом в GraphQL.

В этом руководстве мы изучим методы возврата карты из запроса GraphQL.

2. Пример

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

Продукт , как объект базы данных, может иметь некоторые фиксированные поля, такие как имя , цена , категория и т. д. Но он также может иметь атрибуты, которые варьируются от категории к категории. Эти атрибуты должны быть возвращены клиенту таким образом, чтобы их идентифицирующие ключи оставались сохраненными.

Для этой цели мы можем использовать Map в качестве типа этих атрибутов.

3. Карта возврата

Чтобы вернуть Карту, у нас есть три варианта:

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

query {
product(id:1){
id
name
description
attributes
}
}

Атрибуты параметров будут представлены в формате карты .

Далее рассмотрим все три варианта.

3.1. JSON-строка

Это самый простой вариант. Мы сериализуем карту в формат строки JSON в преобразователе продукта :

String attributes = objectMapper.writeValueAsString(product.getAttributes());

Сама схема GraphQL выглядит следующим образом:

type Product {
id: ID
name: String!
description: String
attributes:String
}

Вот результат запроса после этой реализации:

{
"data": {
"product": {
"id": "1",
"name": "Product 1",
"description": "Product 1 description",
"attributes": "{\"size\": {
\"name\": \"Large\",
\"description\": \"This is custom attribute description\",
\"unit\": \"This is custom attribute unit\"
},
\"attribute_1\": {
\"name\": \"Attribute1 name\",
\"description\": \"This is custom attribute description\",
\"unit\": \"This is custom attribute unit\"
}
}"
}
}
}

Этот вариант имеет две проблемы. Первая проблема заключается в том, что строку JSON необходимо преобразовать на стороне клиента в рабочий формат. Вторая проблема заключается в том, что у нас не может быть подзапроса по атрибутам.

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

3.2. Пользовательский скалярный тип GraphQL

Для реализации мы будем использовать библиотеку Extended Scalars для GraphQL в Java.

Во- первых, мы включим зависимость graphql-java-extended- scalars в pom.xml :

<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>2022-04-06T00-10-27-a70541e</version>
</dependency>

Затем мы зарегистрируем скалярный тип по нашему выбору в компоненте конфигурации GraphQL. В данном случае скалярный тип — JSON :

@Bean
public GraphQLScalarType json() {
return ExtendedScalars.Json;
}

Наконец, мы соответствующим образом обновим нашу схему GraphQL:

type Product {
id: ID
name: String!
description: String
attributes: JSON
}
scalar JSON

Вот результат после этой реализации:

{
"data": {
"product": {
"id": "1",
"name": "Product 1",
"description": "Product 1 description",
"attributes": {
"size": {
"name": "Large",
"description": "This is custom attribute description",
"unit": "This is a custom attribute unit"
},
"attribute_1": {
"name": "Attribute1 name",
"description": "This is custom attribute description",
"unit": "This is a custom attribute unit"
}
}
}
}
}

При таком подходе нам не нужно будет обрабатывать карту атрибутов на стороне клиента. Однако у скалярных типов есть свои ограничения.

В GraphQL скалярные типы — это листья запроса, что говорит о том, что их нельзя запрашивать дальше.

3.3. Список пар ключ-значение

Если требуется выполнить дальнейший запрос в Map , то это наиболее возможный вариант. Мы преобразуем объект Map в список объектов пары ключ-значение.

Вот наш класс, представляющий пару ключ-значение:

public class AttributeKeyValueModel {
private String key;
private Attribute value;

public AttributeKeyValueModel(String key, Attribute value) {
this.key = key;
this.value = value;
}
}

В преобразователь продукта мы добавим следующую реализацию:

List<AttributeKeyValueModel> attributeModelList = new LinkedList<>();
product.getAttributes().forEach((key, val) -> attributeModelList.add(new AttributeKeyValueModel(key, val)));

Наконец, мы обновим схему:

type Product {
id: ID
name: String!
description: String
attributes:[AttributeKeyValuePair]
}
type AttributeKeyValuePair {
key:String
value:Attribute
}
type Attribute {
name:String
description:String
unit:String
}

Поскольку мы обновили схему, мы также обновим запрос:

query {
product(id:1){
id
name
description
attributes {
key
value {
name
description
unit
}
}
}
}

Теперь посмотрим на результат:

{
"data": {
"product": {
"id": "1",
"name": "Product 1",
"description": "Product 1 description",
"attributes": [
{
"key": "size",
"value": {
"name": "Large",
"description": "This is custom attribute description",
"unit": "This is custom attribute unit"
}
},
{
"key": "attribute_1",
"value": {
"name": "Attribute1 name",
"description": "This is custom attribute description",
"unit": "This is custom attribute unit"
}
}
]
}
}
}

Этот вариант также имеет две проблемы. Запрос GraphQL стал немного сложным. И структура объекта должна быть жестко запрограммирована. Неизвестные объекты Map в этом случае работать не будут.

4. Вывод

В этой статье мы рассмотрели три различных метода возврата объекта Map из запроса GraphQL. Мы обсудили ограничения каждого из них. Поскольку ни одна из методик не идеальна, их необходимо использовать исходя из требований.

Как всегда, пример кода для этой статьи доступен на GitHub .