GraphQL — это язык запросов с открытым исходным кодом, созданный Facebook в качестве альтернативы распространенной архитектуре REST. Он позволяет пользователям запрашивать конкретные данные и таким образом избежать классической проблемы избыточности данных в ответах, связанной с REST. В этой статье мы рассмотрим Spring для GraphQL, преемника проекта GraphQL Java Spring от команды GraphQL Java. Проект находится на этапе подготовки к выпуску версии 1.0.0 (релиз запланирован на 17 мая). (Прим. переводчика. Апдейт по дате выпуска: 20 апреля 2023 года произошел релиз Spring for GraphQL версии 1.2.0-RC1). Я предполагаю, что читатели этой статьи обладают базовым пониманием GraphQL. Обращайтесь к официальной документации, если необходимо. Примеры проектов Spring boot можно найти здесь. Настоятельно рекомендую взглянуть на них, прежде чем продолжить. Операция GraphQL — это либо запрос (Query; чтение), либо мутация (Mutation; запись), либо подписка (Subscription; для получения изменений в реальном времени). Как показано выше, результаты возвращаются в форме, соответствующей запросу (вы получаете то, что просите). Запросы GraphQL могут быть отправлены через HTTP POST или HTTP GET. Запросы, отправленные по HTTP POST, включают детали в виде JSON в теле запроса. Статус ответа HTTP всегда 200 OK, а любая замеченная ошибка при выполнении запроса GraphQL отображается в разделе "ошибки" ответа GraphQL. В GraphQL java выполнение запроса заключается в создании объекта GraphQl с соответствующими аргументами и последующем вызове его метода execute или executeAsync. Объект GraphQl строится путем передачи схемы GraphQl, которая определяет каждое поле, которое можно запросить или изменить, и типы этих полей. Схемы могут быть определены программно или с помощью специального graphql dsl. Каждое определение поля имеет Datafetcher (см. раздел runtime wiring). Если таковой не настроен, используется PropertyDataFetcher. PropertyDataFetcher проверяет Maps и POJO Java beans на наличие значений, соответствующих нужному имени. Хотя GraphQL предлагает множество преимуществ по сравнению с REST API, он подвержен проблеме N+1. Это связано с природой фетчеров, которые не знают о том, что происходит за пределами их мира. GraphQL может исполнять отдельный фетчер данных для каждого встречающегося поля, что приводит к ненужным циклам для извлечения вложенных данных. К счастью, Facebook предложил решение для этой проблемы: Dataloaders. DataLoaders может объединять исходящие запросы в один запрос и откладывать выборку данных. Вместо того, чтобы сначала разрешить дочерние поля, фетчер отвечает обещанием (Promise), что данные в конечном итоге будут возвращены, откладывает выполнение запроса и переходит к следующему фетчеру на том же уровне. Как только все дочерние поля будут просмотрены (по принципу Depth First Traversal), выполняется один запрос для получения данных для всех этих полей (размер и порядок должны быть сохранены в этом случае). В этом разделе мы подробно рассмотрим реализацию Spring GraphQl, которая построена на основе graphql-java. Spring GraphQL поддерживает запросы GraphQL через HTTP и WebSockets. Мы сосредоточимся на обработке HTTP-запросов. GraphQlHttpHandler обрабатывает GraphQL через HTTP-запросы и делегирует веб-обработчику выполнение запросов. Есть два варианта, один для Spring MVC и один для Spring WebFlux. Мы перенаправим наше внимание на Spring WebFlux. GraphQlHttpHandler может быть связан как HTTP путем объявления бина (bean) RouterFunction и использования RouterFunctions из Spring MVC или WebFlux для создания маршрутов (см. раздел Spring GraphQl Configuration). Как показано ниже, когда запрос был получен, применяется следующая логика: инстанцирование WebGraphQlRequest делегирование GraphQlHanlder для обработки запроса возврат ответа сервера DefaultWebGraphQlHandlerBuilder — это реализация по умолчанию для создания экземпляров WebGraphQlHandler. Метод build показан ниже. Цепочка выполнения строится с использованием настроенных перехватчиков, запрос в итоге делегируется ExecutionGraphQlService. Используя заданные аксессоры ThreadLocal значения извлекаются и сохраняются в Reactor контексте. Контекст Reactor представляет собой неизменяемый Map или хранилище ключей/значений. Он привязывается к каждой последовательности и передается вверх через подписку. Эта функция уникальна для Reactor и не работает с другими спецификациями реактивных потоков. DefaultExecutionGraphQlService — это ExecutionGraphQlService, который использует GraphQlSource для получения экземпляра GraphQL и выполнения запроса. Ниже показан его метод execute. Он состоит из: Поиска по контексту и сохранение контекста Reactor executionInput для последующего доступа через DataFetchingEnvironment. Регистрации Dataloaders для executionInput, в результате чего возвращаются обновленные executionInput для выполнения. Асинхронного выполнения запроса и сопоставления его с ответом graphql. GraphQlSource — это основная абстракция Spring GraphQL для доступа к экземплярам graphql.GraphQL request execution. Он предоставляет builder API для инициализации GraphQL Java и создания GraphQlSource. GraphQlSource поддерживает Reactive DataFetcher, Context Propagation и Exception Resolution через стандартный сборщик, доступный через GraphQlSource.builder(). Реализация GraphQlSource.Builder по умолчанию, которая инициализирует экземпляр GraphQL и оборачивает его возвращаемым GraphQlSource. Метод build показан ниже. Применяется следующая логика: Получение определения схемы — заключается в парсинге dsl-файла схемы. RuntimeWiring инициализируется, runTimeConfigurers вносит вклад в конфигурацию runtimeWiring. Регистрируется преобразователь типов по умолчанию, возвращающийся к ClassNameTypeResolver. Применяется визитор типов. GraphQLCodeRegistry содержит тот код выполнения, который связан с типами graphql, а именно DataFetchers, связанные с полями, TypeResolvers, связанные с абстрактными типами и GraphqlFieldVisibility. Наконец, создается экземпляр GraphQL и оборачивается в CachedGraphQlSource. Ключевым компонентом серверов GraphQL Java является DataFetcher. Когда GraphQL Java выполняет запрос, он вызывает настроенный DataFetcher для каждого поля в запросе. DataFetcher – это интерфейс с единственным методом, принимающим один параметр типа DataFetcherEnvironment Перенаправляем внимание на ReactiveSingleEntityFetcher и ReactiveManyEntityFetcher. Метод get ReactiveSingleEntityFetcher показан выше; используя DataFetchingEnvironment, он делегирует его QuerydslPredicateExecutor (см. раздел GraphQL Querydsl). Кроме того, добавляются операторы, основанные на конфигурации запроса. ReactiveManyEntityFetcher очень похож, единственное отличие заключается в том, что вместо Mono возвращается Flux publisher. Spring for GraphQL поддерживает использование Querydsl для вызова данных через расширение Spring Data Querydsl. Querydsl — это фреймворк, который позволяет строить статически типизированные SQL-подобные запросы с помощью своего API. Несколько модулей Spring Data предлагают интеграцию с Querydsl через QuerydslPredicateExecutor. Например, мы можем объявить репозиторий как QuerydslPredicateExecutor: Затем использовать его для создания DataFetcher. DataFetcher строит Querydsl Predicate из параметров запроса GraphQL и использует его для получения данных. Spring Data поддерживает QuerydslPredicateExecutor для JPA, MongoDB и LDAP. Если репозиторий является ReactiveQuerydslPredicateExecutor, builder возвращает DataFetcher<Mono<Account>> или DataFetcher<Flux<Account>>. Wiring здесь в основном означает подключение поля к сборщику данных. RuntimeWiringConfigurer можно использовать для регистрации: Пользовательских скалярных типов. Кода обработки директив. TypeResolver, если нужно переопределить Default TypeResolver для типа. DataFetcher для поля, хотя большинство приложений будут просто конфигурировать AnnotatedControllerConfigurer, который обнаруживает аннотированные методы-обработчики DataFetcher. Spring Boot стартер добавляет AnnotatedControllerConfigurer по умолчанию. AnnotatedControllerConfigurer — это RuntimeWiringConfigurer, он будет влиять на конфигурацию RuntimeWiring. Он обнаруживает аннотированные методы-обработчики SchemaMapping в классах Controller и регистрирует их как DataFetchers. Аннотация @SchemaMapping сопоставляет метод обработчика с полем в схеме GraphQL и объявляет его DataFetcher для этого поля. Как показано ниже, он сканирует бины в контексте приложения и выбирает те классы бинов, которые аннотированы аннотацией Contoller. Затем выполняется проверка для поиска методов обработчика, аннотированных @SchemaMapping или @BatchMapping. @BatchMapping используется для пакетной загрузки. HandlerMethod инкапсулирует информацию о методе обработчика, состоящем из методов getMethod() и getBean(). Он обеспечивает удобный доступ к параметрам метода, возвращаемому значению метода, аннотациям метода и т.д. В зависимости от наличия @BatchMapping или @SchemaMapping, регистрируется BatchMappingDataFetcher или SchemaMappingDataFetcher соответственно. Обратите внимание, что при сканировании контекста приложения мы получаем имя бина (String), а не экземпляр, в этом случае мы откладываем установку объекта бина, соответствующего методу обработчика, на потом. CreateHandlerMethod показан ниже. Когда мы проходим через метод-кандидат, мы проверяем, является ли бин-обработчик String. Если это так, мы вызываем соответствующий конструктор. createWithResolvedBean вызовем позже, чтобы установить бин, обернутый внутри метода обработчика. Хорошим примером будет вызов метода SchemaMappingDataFetcher#get, который создаст новый DataFetcherHandlerMethod (InvocableHandlerMethodSupport) Как показано ниже, это приведет к тому, что bean будет разрешен в контексте приложения. На самом деле, если мы выполняем запрос, это означает, что приложение Spring запущено и экземпляр bean находится там. GraphQlRepository — это специализация стереотипа Repository, которая маркирует хранилище как предназначенное для использования в GraphQL-приложении для получения данных. Если хранилище аннотировано при помощи @GraphQlRepository, оно будет автоматически зарегистрировано для запросов, не имеющих уже зарегистрированного DataFetcher. Учитывая карту имен типов GraphQL и фабрик DataFetcher, AutoRegistrationRuntimeWiringConfigurer находит запросы с соответствующим возвращаемым типом и регистрирует для них DataFetcher'ы, если они еще не зарегистрированы. Его метод configure показан ниже, он добавляет WiringFactory; AutoRegistrationWiringFactory. AutoRegistrationWiringFactory getDataFetcher показан ниже. По умолчанию имя типа GraphQL, возвращаемого запросом, должно совпадать с простым именем типа домена хранилища. DataFetcher'ы будут зарегистрированы, если у них уже нет регистраций. Boot starter автоматически обнаруживает бины @GraphQlRepository и использует их для инициализации RuntimeWiringConfigurer. В GraphQL Java DataFetchingEnvironment предоставляет доступ к специфическим для поля значениям аргументов. Аргументы доступны как простые скалярные значения, такие как String, или как Map of values для более сложного ввода, или как List of values. @Argument можно использовать для доступа к аргументу поля, который сопоставляется с методом обработчика. ArgumentMapMethodArgumentResolver делегирует GraphQlArgumentInitializer для инстанцирования целевого типа и связывания данных из аргументов graphql.schema.DataFetchingEnvironment. Затем метод вызывается вместе с разрешенными аргументами. Если тип аргумента — map и мы имеем дело с классами java bean, он будет делегирован DataBinder после правильного извлечения значений свойств, которые будут переданы для связывания. GraphQlArgumentBinder связывает аргумент GraphQL или полную карту аргументов с целевым объектом. Его метод bind показан ниже Применяется следующая логика: Используя среду выборки данных, получаем необработанное значение. Затем необходимо определить класс, который используем для привязки. Для преобразования исходных данных в целевой тип используем TypeConverter (по умолчанию SimpleTypeConverter). Выполняем окончательную проверку привязки (проверяем, не возникла ли какая-либо ошибка). Возвращаем целевой объект. Аннотация @ContextValue предоставляет удобный доступ к значениям в GraphQLContext, которые могут быть разрешены в качестве аргументов методов контроллера. ContextValueMethodArgumentResolver — это преобразователь для аннотированных параметров метода ContextValue. В качестве альтернативы использованию полных объектов с @Argument также можно использовать интерфейс проекции для доступа к аргументам запроса GraphQL через четко определенный, минимальный интерфейс. Проекции аргументов предоставляются проекциями интерфейсов Spring Data, когда Spring Data находится на пути класса. ProjectedPayloadMethodArgumentResolver — это преобразователь для получения ProjectedPayload @ProjectedPayload либо на основе полной карты DataFetchingEnvironment#getArguments(), либо на основе определенного аргумента внутри карты, когда параметр метода аннотирован с @Argument. Чтобы воспользоваться этим, создайте интерфейс, аннотированный @ProjectedPayload, и объявите его в качестве параметра метода контроллера. Если параметр аннотирован с @Argument, он применяется к отдельному аргументу в карте DataFetchingEnvironment.getArguments(). Если параметр объявлен без @Argument, проекция действует на аргументы верхнего уровня в полной карте аргументов. В этом разделе мы рассмотрим конфигурацию бинов, добавляемую стартером для разработки приложения Spring WebFlux. Класс GraphQlWebFluxAutoConfiguration GraphQlWebFluxAutoConfiguration — это класс конфигурации, позволяющий использовать Spring GraphQL через WebFlux. Он объявляет GraphQL RouterFunction bean для заполнения в контексте приложения. В Spring WebFlux бины типа RouterFunction собираются и получают делегированный трафик, если запрос соответствует. GraphQlAutoConfiguration — это класс конфигурации для создания базовой инфраструктуры Spring GraphQL. GraphQlSource конфигурируется ниже. Сначала он передаст ресурсы схемы и настроит конфигураторы wiring, найденные в контексте приложения, такие как AnnotatedControllerConfigurer. Кроме того, GraphQlAutoConfiguration объявляет два условных бина типа ExecutionGraphQlService и BatchLoaderRegistry. В этой статье мы рассмотрели реализацию Spring GraphQl, сосредоточились на WebFlux для построения реактивных веб-приложений, однако проекты обеспечивают поддержку других протоколов связи, таких как RSocket. Первый и последний релиз-кандидат Spring for GraphQL 1.0 уже доступен по ссылке.  Приглашаем всех желающих на открытое занятие «События в Spring Data JPA». На нем затронем такую важную тему, как работа с событиями, генерируемыми при взаимодействии с JPA-сущностями. Записаться на занятие можно на странице онлайн-курса «Разработчик на Spring Framework».