Если query — это способ получать данные, то mutation — это способ изменять данные на сервере. Аналогично методам POST, PUT или DELETE в REST, мутации позволяют добавлять, обновлять или удалять данные.
Основные отличия мутаций от запросов (queries)
- Семантика действия: мутации предназначены для модификации состояния сервера.
- Расположение в схеме: мутации определяются отдельным блоком
mutationв схеме GraphQL. - Тип ответа: как и запросы, мутации возвращают данные. Однако возвращаемые данные часто содержат результат выполненного изменения (например, обновлённый объект).
Основы работы с мутациями
Для начала определим, как мутации выглядят в самом GraphQL. Рассмотрим пример:
Пример: создание нового пользователя
Схема GraphQL:
type Mutation {
createUser(input: CreateUserInput): User
}
input CreateUserInput {
name: String!
email: String!
}
type User {
id: ID!
name: String!
email: String!
}
Здесь:
- Mutation описывает операцию изменения данных.
- Input используется для передачи аргументов в
createUser. Это удобно, так как позволяет группировать параметры. - User представляет тип возвращаемого объекта.
Запрос мутации:
mutation {
createUser(input: { name: "Сергей", email: "sergey@example.com" }) {
id
name
email
}
}
Ответ сервера:
{
"data": {
"createUser": {
"id": "1",
"name": "Сергей",
"email": "sergey@example.com"
}
}
}
Реализация мутаций в Spring Boot
Теперь давайте реализуем createUser в вашем Spring Boot приложении.
Шаг 1: Определение схемы
Создайте файл schema.graphqls в папке src/main/resources/graphql. Добавьте следующую схему:
type Mutation {
createUser(input: CreateUserInput): User
}
input CreateUserInput {
name: String!
email: String!
}
type User {
id: ID!
name: String!
email: String!
}
Эта схема описывает вводимые данные (CreateUserInput) и возвращаемый объект (User).
Шаг 2: Создание модели данных
Создайте класс User для представления сущности пользователя:
package com.example.graphql.model;
public class User {
private String id;
private String name;
private String email;
// Конструкторы
public User() {}
public User(String id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Геттеры и сеттеры
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Шаг 3: Реализация резолвера мутаций
Создайте класс резолвера:
package com.example.graphql.resolver;
import com.example.graphql.model.User;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
public class MutationResolver {
public User createUser(String name, String email) {
// Создаём нового пользователя (в реальном приложении данные шли бы в базу данных)
return new User(UUID.randomUUID().toString(), name, email);
}
}
Этот резолвер создаёт нового пользователя и возвращает его. Для генерации уникального id мы используем UUID.
Шаг 4: Связывание схемы и резолвера
Свяжем схему с резолвером с помощью GraphQLMutationResolver:
package com.example.graphql.resolver;
import com.example.graphql.model.User;
import graphql.kickstart.tools.GraphQLMutationResolver;
import org.springframework.stereotype.Component;
@Component
public class UserMutationResolver implements GraphQLMutationResolver {
public User createUser(String name, String email) {
// Логика создания пользователя из резолвера
return new User(UUID.randomUUID().toString(), name, email);
}
}
Здесь:
- Мы реализуем интерфейс
GraphQLMutationResolver, который автоматически связывает мутации с GraphQL.
Шаг 5: Тестирование мутаций
Запустите приложение и перейдите в GraphQL Playground (или другой инструмент, например Insomnia). Выполните следующий запрос:
mutation {
createUser(input: { name: "Анна", email: "anna@example.com" }) {
id
name
email
}
}
Ожидаемый ответ:
{
"data": {
"createUser": {
"id": "cd3f7628-1137-45a4-85ba-ecfc988b0278",
"name": "Анна",
"email": "anna@example.com"
}
}
}
Обработка ошибок и валидация
Когда дело доходит до мутаций, важно позаботиться о двух вещах:
- Валидация входных данных.
- Обработка ошибок.
Для валидации данных мы можем использовать библиотеку javax.validation. Например:
Шаг 1: Добавьте аннотации в Input-класс
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
public class CreateUserInput {
@NotBlank(message = "Имя не может быть пустым")
private String name;
@Email(message = "Некорректный формат email")
private String email;
// Геттеры и сеттеры...
}
Шаг 2: Включите валидацию в резолвер
import javax.validation.Valid;
@Component
public class UserMutationResolver implements GraphQLMutationResolver {
public User createUser(@Valid CreateUserInput input) {
return new User(UUID.randomUUID().toString(), input.getName(), input.getEmail());
}
}
При некорректных данных сервер вернёт ответ с сообщением об ошибке.
Пример ошибки:
{
"errors": [
{
"message": "Имя не может быть пустым",
"locations": [...],
"path": [...]
}
]
}
Заключительные советы
- Мутации похожи на POST/PUT в REST: если вы уже писали REST-контроллеры, работа с мутациями покажется интуитивно понятной.
- Не забывайте про валидацию: она избавит вас от головной боли при обработке входных данных.
- Обрабатывайте ошибки грамотно: возвращайте клиенту понятные сообщения, чтобы им не пришлось обращаться к вашему Slack'у!
В следующей лекции мы углубимся в работу с Data Fetchers, которые помогут сделать наши запросы ещё более гибкими!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ