JavaRush /Курсы /Модуль 5. Spring /Лекция 286: Введение в Data Fetchers и их роль в GraphQL

Лекция 286: Введение в Data Fetchers и их роль в GraphQL

Модуль 5. Spring
29 уровень , 5 лекция
Открыта

Представьте, что вы в ресторане, официант приносит вам меню, и вы выбираете блюда. Официант записывает ваш заказ, отправляется на кухню, где шеф-повар готовит блюда. Затем он приносит их обратно к вам. В мире 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, который связывает вашу схему с источниками данных и помогает обработать каждый запрос с максимальной гибкостью. С их помощью можно не только масштабировать приложение, но и значительно улучшить производительность и гибкость, особенно в микросервисной архитектуре.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ