Представьте, что вы в ресторане, официант приносит вам меню, и вы выбираете блюда. Официант записывает ваш заказ, отправляется на кухню, где шеф-повар готовит блюда. Затем он приносит их обратно к вам. В мире GraphQL Data Fetchers — это официанты, которые "приносят данные" для запросов. Они выполняют запросы, извлекают данные из баз данных, REST API или других источников и передают их обратно клиенту.
Data Fetcher — это механизм, отвечающий за реализацию того, как данные, описанные в GraphQL схеме, будут получены. Он является мостом между вашей схемой и реальными данными.
Почему Data Fetchers так важны?
Когда клиент делает запрос к GraphQL API, сервер обращается к соответствующему Data Fetcher'у для каждого поля в запросе. В отличие от REST или обычных сервисов, где вы, как разработчик, обычно реализуете только одну точку доступа для возвращения данных, GraphQL требует более тонкого подхода: для каждого поля может быть своя логика получения данных.
Data Fetchers позволяют гибко настроить как именно и где будут взяты данные: из базы данных, других API, кэша или даже обработаны локально.
Различия между стандартными резолверами и Data Fetchers
В предыдущих лекциях мы уже познакомились с Query и Mutation Resolvers. На самом деле, э ти резолверы являются базовыми Data Fetchers, которые выполняют обработку всех запросов на верхнем уровне. Однако, если мы углубимся, становится понятно, что Data Fetchers предоставляют гораздо больший контроль в определении деталей обработки.
Но когда использовать Query Resolvers, а когда перейти на Data Fetchers? Вот несколько случаев:
- Если ваши данные зависят от полей внутри типа (например, нужно рассчитать что-то на основе полученных данных), то вам потребуются Data Fetchers.
- Если данные для разных полей в одном типе должны поступать из разных источников.
- Если вам нужна кастомная логика именно для конкретных полей или вы интегрируете данные из многих источников.
Практическое использование Data Fetchers
1. Реализация базового Data Fetcher
Начнем с классического простого примера. Представим, что у нас есть Book объект в нашей системе. В GraphQL схеме это может выглядеть так:
type Book {
id: ID!
title: String!
author: Author!
}
Тип Author:
type Author {
id: ID!
name: String!
}
И GraphQL запрос:
query {
book(id: 1) {
title
author {
name
}
}
}
В данном случае title и author.name должны быть обработаны через Data Fetchers.
1.1 Определяем наш Data Fetcher для title
@Component
public class TitleDataFetcher implements DataFetcher<String> {
private final BookService bookService;
public TitleDataFetcher(BookService bookService) {
this.bookService = bookService;
}
@Override
public String get(DataFetchingEnvironment environment) throws Exception {
// Получаем ID книги из аргументов запроса
Integer bookId = environment.getArgument("id");
Book book = bookService.getBookById(bookId);
return book.getTitle();
}
}
Здесь метод get получает DataFetchingEnvironment, который предоставляет всю информацию о текущем запросе: аргументы, контекст и так далее.
1.2 Настраиваем Data Fetcher в GraphQL Schema
Создадим schema.graphqls:
type Query {
book(id: ID!): Book
}
type Book {
id: ID!
title: String!
author: Author!
}
type Author {
id: ID!
name: String!
}
Подключаем Data Fetcher в RuntimeWiring:
@Configuration
public class GraphQLConfiguration {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(TitleDataFetcher titleDataFetcher) {
return wiring -> wiring
.type("Book", builder -> builder
.dataFetcher("title", titleDataFetcher));
}
}
2. Интеграция вызываемых источников (например, REST API)
Представьте, что author — это отдельный микросервис, который возвращает данные об авторе. Теперь мы создадим Data Fetcher для Author.
@Component
public class AuthorDataFetcher implements DataFetcher<Author> {
private final RestTemplate restTemplate;
public AuthorDataFetcher(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public Author get(DataFetchingEnvironment environment) throws Exception {
// Получаем родительский объект (Book)
Book book = environment.getSource();
// Получаем автора книги из другого сервиса
ResponseEntity<Author> response = restTemplate.getForEntity(
"http://author-service/authors/" + book.getAuthorId(), Author.class);
return response.getBody();
}
}
3. Кастомная обработка данных
Иногда вам может понадобиться включить кастомную логику. Например, нужно вернуть поле rating, которое вычисляется динамически.
@Component
public class RatingDataFetcher implements DataFetcher<Double> {
@Override
public Double get(DataFetchingEnvironment environment) throws Exception {
// Генерируем случайный рейтинг от 1 до 5
return Math.random() * 5 + 1;
}
}
Сложные случаи: когда Data Fetchers действительно помогают
1. Агрегация данных из нескольких источников
Допустим, ваш GraphQL запрос запрашивает поле reviews для книги, которое находится в другой базе данных. Вместо того чтобы изменять основной BookResolver, вы можете создать отдельный Data Fetcher для reviews.
@Component
public class ReviewsDataFetcher implements DataFetcher<List<Review>> {
private final ReviewsService reviewsService;
public ReviewsDataFetcher(ReviewsService reviewsService) {
this.reviewsService = reviewsService;
}
@Override
public List<Review> get(DataFetchingEnvironment environment) throws Exception {
// Получаем родительский объект (Book)
Book book = environment.getSource();
return reviewsService.getReviewsForBook(book.getId());
}
}
2. Множественные Data Fetchers на одном поле
Иногда вы можете захотеть настроить разные Data Fetchers в зависимости от каких-то условий. Например, поле price может быть взято из разных источников:
- Если это книга на складе — из локальной базы.
- Если это цифровая копия — из внешнего API.
Типичные ошибки
Работая с Data Fetchers, новички часто допускают следующие ошибки. Например, забывают об оптимизации запросов. Если вы вызываете 10 Data Fetchers для одного запроса, и каждый из них делает вызов в базу данных, это может привести к серьезным проблемам производительности. Решение — использование batch loaders (о них мы поговорим в будущем), чтобы минимизировать количество запросов.
Также нередко забывают об обработке исключений. Если ваш Data Fetcher бросает исключение, это может "завалить" весь запрос. Используйте DataFetcherExceptionHandler для управления.
Подводя итоги
Data Fetchers — это ключевой инструмент в GraphQL, который связывает вашу схему с источниками данных и помогает обработать каждый запрос с максимальной гибкостью. С их помощью можно не только масштабировать приложение, но и значительно улучшить производительность и гибкость, особенно в микросервисной архитектуре.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ