Якщо 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, які допоможуть зробити наші запити ще гнучкішими!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ