Начнем с официального определения GraphQL:

GraphQL — это язык запросов для API и среда выполнения для выполнения этих запросов с вашими существующими данными. GraphQL предоставляет полное и понятное описание данных в вашем API, дает клиентам возможность запрашивать именно то, что им нужно, и ничего больше, упрощает постепенное развитие API и предоставляет мощные инструменты для разработчиков.

В этой статье мы увидим, что такое GraphQL, преимущества использования GraphQL путем создания простого сервера GraphQL с Spring Boot и MongoDB, а также его преимущества по сравнению с реализацией REST.

С приведенным выше официальным определением мы можем понять следующие пункты

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

GraphQL позволяет нам определять структуру и формат данных, которые нам нужно получить, а также позволяет запрашивать несколько моделей в одном запросе.

Схема GraphQL будет определена в файле с расширением «.graphqls», который содержит типы Schema, Query, Mutation, Object.

Как GraphQL устраняет недостатки REST API:

  • GraphQL предоставляет потребителям возможность запрашивать атрибуты по мере необходимости, т. е. предоставляет динамические данные в запросе. Ответ REST API всегда фиксирован, так как он отправляет весь объект JSON для каждого запроса.
  • REST API имеет проблемы с избыточной и недостаточной выборкой.
  • Лучшая производительность с меньшим количеством кода.
  • GraphQL предоставляет единую конечную точку для всех операций с запросами и мутациями, а также метод HTTP POST. В REST каждая операция имеет отдельный API, использующий разные HTTP-глаголы (GET, PUT, POST, DELETE) в соответствии с требованиями.
  • GraphQL зависит от файла схемы, определенного для приложения. Потребителям будут доступны только свойства или операции, указанные в файле схемы.

Схема GraphQL:

Схема представляет GraphQL API с точки зрения операций запроса и мутации, а также их типов и свойств.

schema {
    query : GadgetQuery
    mutation: GadgetMutation
}

Тип запроса:

Тип запроса — это корневой тип в схеме GraphQL, используемый для создания запроса на получение данных с сервера GraphQL. Он обычно используется для определения операций только для чтения (GET). Давайте рассмотрим пример приложения галереи гаджетов. Тип запроса можно использовать для определения набора операций, как показано ниже.

Примечание. Массив или список объектов обозначаются парой квадратных/круглых скобок, например: [Гаджет] и восклицательный знак указывают на его обязательное/обязательное свойство.

Выбрать гаджет по GadgetId
Выбрать все гаджеты
Выбрать гаджеты по категориям
Выбрать доступные гаджеты по категориям

## Root type
type GadgetQuery {
    application : String
    gadget(gadgetId : String!) : GadgetResponse
    allGadgets : [GadgetResponse]!
}

Тип мутации:

Тип мутации — это еще один корневой тип в схеме GraphQL, используемый для определения операций по управлению данными (POST, PUT, DELETE). Давайте рассмотрим тот же пример приложения галереи гаджетов. Тип мутации можно использовать для определения операций, как показано ниже.

type GadgetMutation {
    persistGadgetData(gadgetInput : GadgetInput) : GadgetResponse
}

Тип объекта:

Тип объекта используется для определения определяемых пользователем типов (класс в Java/Kotlin) со свойствами и типами данных, используемыми в качестве параметра ввода/вывода для выполнения операций с типами запросов и мутаций, как определено в файле схемы.

Пример.Тип гаджета можно определить в файле схемы, как показано ниже.

Примечание: восклицательный знак указывает на обязательный/обязательный параметр. например: Строка!

type Gadget {
    gadgetId: String!
    gadgetName: String!
    gadgetCategory: String
    gadgetAvailability: Boolean!
    gadgetPrice: Float!
}

Резолверы GraphQL:

  • GraphQLQueryResolver:

Это один из основных преобразователей, используемых для разрешения операций типа Query. Он действует как отправная точка для всех операций только для чтения, определенных в приложении (файле схемы).

GadgetQueryResolver реализует GraphQLQueryResolver и обеспечивает реализацию операций запросов, определенных в файле схемы. Spring сканирует структуру пакета на наличие bean-компонентов распознавателя и вызывает правильный метод, объявленный в файле схемы.

class GadgetQueryResolver : GraphQLQueryResolver {...}
  • GraphQLMutationResolver:

Это еще один основной преобразователь, используемый для разрешения операций типа Mutation. Он действует как отправная точка для всех операций изменения данных, определенных в файле схемы GraphQL (т. е. операций POST, PUT и DELETE).

Класс GadgetMutationResolver реализует GraphQLMutationResolver и предоставляет реализацию для всех методов изменения, определенных в файле схемы.

class GadgetMutationResolver : GraphQLMutationResolver {...}
  • GraphQLResolver:

Он действует как универсальный преобразователь для всех других типов (настраиваемых пользовательских типов) в файле схемы GraphQL. Это особенно используется, когда у нас есть несколько классов моделей, связанных друг с другом некоторыми сопоставлениями. Для определения типа используются дженерики.

class PhoneTypeResolver : GraphQLResolver<Phone> {...}

Сервер GraphQL: практический пример

Технологии/инструменты:

IDE: IntelliJ (STS/Eclipse)
Kotlin: 1.4.x
GraphQL и GraphiQL: 5.xx
Инструмент сборки: Maven
Spring Boot: 2.4.x
База данных: сервер MongoDB (или любая другая база данных — H2 или MySQL)

Spring Boot + MongoDB + Kotlin config

Добавьте ниже зависимости GraphQL Maven

<dependencies>
   ...
   <!-- Enable GraphQL servlet-mapping at URI - '/graphql' -->
   <dependency>
      <groupId>com.graphql-java-kickstart</groupId>
      <artifactId>graphql-spring-boot-starter</artifactId>
      <version>5.7.0</version>
   </dependency>

   <!-- GraphQL UI at '/graphiql' by default - GraphQL testing -->
   <dependency>
      <groupId>com.graphql-java-kickstart</groupId>
      <artifactId>graphiql-spring-boot-starter</artifactId>
      <version>5.5.0</version>
   </dependency>

   <dependency>
      <groupId>com.graphql-java-kickstart</groupId>
      <artifactId>graphql-java-tools</artifactId>
      <version>5.4.1</version>
   </dependency>
   ...
</dependencies>

Конфигурация приложения — application.yml

Здесь мы переопределяем URL-адрес graphiql по умолчанию с «graphiql» на «gadget-ui» и предоставляем конфигурацию MongoDB.

## Server port
server:
  port: 9997

## Graphiql Mapping
graphiql:
  mapping: gadget-ui

## MongoDB configuration (optional - if not provided spring boot takes default values)
spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      database: srsgadgetdb

Репозиторий Mongo — для подключения сервера MongoDB

@Repository
interface GadgetRepository : MongoRepository<Gadget?, String?> { }

Давайте посмотрим полную схему GraphQL для приложения галереи гаджетов «gadget.graphqls», помещенного в путь к классам проекта.

schema {
    query : GadgetQuery
    mutation: GadgetMutation
}

## Root type
type GadgetQuery {
    application : String
    gadget(gadgetId : String!) : GadgetResponse
    allGadgets : [GadgetResponse]!
}

## Root type
type GadgetMutation {
    persistGadgetData(gadgetInput : GadgetInput) : GadgetResponse
}

type Gadget {
    gadgetId: String!
    gadgetName: String!
    gadgetCategory: String
    gadgetAvailability: Boolean!
    gadgetPrice: Float!
}

type GadgetResponse {
    gadgetId: String!
    gadgetName: String!
    gadgetCategory: String
    gadgetAvailability: Boolean!
    gadgetPrice: Float!
}

input GadgetInput {
    gadgetName: String!
    gadgetCategory: String
    gadgetAvailability: Boolean!
    gadgetPrice: Float!
}

Коллекция гаджетов и детали документа с набором свойств

@Document(collection = "Gadget")
data class Gadget(

    @Id
    val gadgetId: String? = null,
    var gadgetName: String? = null,
    var gadgetCategory: String? = null,
    var gadgetAvailability: Boolean? = true,
    var gadgetPrice: Double? = null
)

GadgetQueryResolver:

В этом классе мы предоставляем реализации для операций, определенных в файле схемы для корневого типа запроса.

@Component
class GadgetQueryResolver : GraphQLQueryResolver {

    @Autowired
    var gadgetRepository: GadgetRepository? = null

    fun application() : String {
        return "Welcome to GraphQL with Kotlin and MongoDB...!"
    }

    fun getGadget(gadgetId: String): GadgetResponse? {
        var gadget = gadgetRepository?.findById(gadgetId)?.get()
        return mapGadgetToGadgetResponse(gadget)
    }

    fun getAllGadgets(): List<GadgetResponse?>? {
        return getGadgetResponse(gadgetRepository?.findAll() as List<Gadget>)
    }

    private fun getGadgetResponse(gadgetList: List<Gadget>): List<GadgetResponse>? {
        val gadgetResponseList: MutableList<GadgetResponse> = ArrayList()
        gadgetList.stream().forEach { gadget: Gadget ->
            gadgetResponseList.add(mapGadgetToGadgetResponse(gadget))
        }
        return gadgetResponseList
    }

    private fun mapGadgetToGadgetResponse (gadget : Gadget?) : GadgetResponse {
        var gadgetResponse = GadgetResponse()
        gadgetResponse.gadgetId = gadget?.gadgetId
        gadgetResponse.gadgetName = gadget?.gadgetName
        gadgetResponse.gadgetCategory = gadget?.gadgetCategory
        gadgetResponse.gadgetPrice = gadget?.gadgetPrice
        gadgetResponse.gadgetAvailability = gadget?.gadgetAvailability
        return gadgetResponse;
    }
}

GadgetMutationResolver:

В этом классе мы предоставляем реализации для операций, определенных в файле схемы для типа Mutation.

@Component
class GadgetMutationResolver : GraphQLMutationResolver {

    @Autowired
    var gadgetRepository: GadgetRepository? = null

    fun persistGadgetData(persistGadget: GadgetInput): GadgetResponse {
        val saveGadgetData = Gadget()
        saveGadgetData.gadgetName = persistGadget.gadgetName
        saveGadgetData.gadgetCategory = persistGadget.gadgetCategory
        saveGadgetData.gadgetPrice = persistGadget.gadgetPrice
        saveGadgetData.gadgetAvailability = persistGadget.gadgetAvailability
        val persistedGadget: Gadget? = gadgetRepository?.save(saveGadgetData)
        return mapGadgetToGadgetResponse(persistedGadget)
    }

    private fun mapGadgetToGadgetResponse (gadget : Gadget?) : GadgetResponse {
        var gadgetResponse = GadgetResponse()
        gadgetResponse.gadgetId = gadget?.gadgetId
        gadgetResponse.gadgetName = gadget?.gadgetName
        gadgetResponse.gadgetCategory = gadget?.gadgetCategory
        gadgetResponse.gadgetPrice = gadget?.gadgetPrice
        gadgetResponse.gadgetAvailability = gadget?.gadgetAvailability
        return gadgetResponse;
    }
}

GraphQL — тестирование приложения-гаджета:

По умолчанию сопоставление сервлета GraphQL будет следующим:
http://localhost:9997/graphiql

Пользовательский URL-адрес на основе конфигурации, предоставленной в файле application.yml: http://localhost:9997/gadget-ui

Тестирование изменения гаджета:

Тестирование запросов гаджета:

Проверьте базу данных MongoDB для коллекции гаджетов, где мы можем найти 3 документа, как показано ниже:

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

Полный код приведенного выше примера можно найти на GitHub.



Официальная документация GraphQL: