Imagina que estás en un restaurante: el camarero te trae la carta y eliges los platos. El camarero apunta tu pedido, va a la cocina donde el chef prepara los platos. Luego te los trae. En el mundo de GraphQL, los Data Fetchers son los camareros que "traen los datos" para las consultas. Ejecutan las solicitudes, extraen datos de bases de datos, REST API u otras fuentes y los devuelven al cliente.
Un Data Fetcher es el mecanismo responsable de cómo se obtendrán los datos descritos en el esquema de GraphQL. Es el puente entre tu esquema y los datos reales.
¿Por qué son tan importantes los Data Fetchers?
Cuando un cliente hace una petición al API GraphQL, el servidor llama al Data Fetcher correspondiente para cada campo en la petición. A diferencia de REST u otros servicios donde normalmente implementas un solo punto de entrada para devolver datos, GraphQL requiere un enfoque más fino: cada campo puede tener su propia lógica de obtención de datos.
Los Data Fetchers permiten configurar con flexibilidad cómo y dónde se obtendrán los datos: desde la base de datos, otros API, la cache o incluso procesados localmente.
Diferencias entre los resolvers estándar y los Data Fetchers
En las lecciones anteriores ya vimos los Query y Mutation Resolvers. En realidad, esos resolvers son Data Fetchers básicos que manejan las peticiones de nivel superior. Sin embargo, si profundizas, verás que los Data Fetchers ofrecen mucho más control a la hora de definir los detalles del procesamiento.
¿Cuándo usar Query Resolvers y cuándo pasarse a Data Fetchers? Aquí algunos casos:
- Si tus datos dependen de campos dentro de un tipo (por ejemplo, tienes que calcular algo basado en los datos recibidos), necesitarás Data Fetchers.
- Si los datos para distintos campos en un mismo tipo deben venir de diferentes fuentes.
- Si necesitas lógica personalizada para campos concretos o integras datos desde muchas fuentes.
Uso práctico de los Data Fetchers
1. Implementación de un Data Fetcher básico
Empecemos con un ejemplo clásico y sencillo. Imagina que tenemos un objeto Book en nuestro sistema. En el esquema de GraphQL esto podría verse así:
type Book {
id: ID!
title: String!
author: Author!
}
El tipo Author:
type Author {
id: ID!
name: String!
}
Y la consulta GraphQL:
query {
book(id: 1) {
title
author {
name
}
}
}
En este caso title y author.name deberían ser manejados vía Data Fetchers.
1.1 Definimos nuestro Data Fetcher para 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 {
// Obtenemos el ID del libro de los argumentos de la petición
Integer bookId = environment.getArgument("id");
Book book = bookService.getBookById(bookId);
return book.getTitle();
}
}
Aquí el método get recibe el DataFetchingEnvironment, que proporciona toda la información sobre la petición actual: argumentos, contexto, etc.
1.2 Configuramos el Data Fetcher en el esquema de GraphQL
Creamos schema.graphqls:
type Query {
book(id: ID!): Book
}
type Book {
id: ID!
title: String!
author: Author!
}
type Author {
id: ID!
name: String!
}
Conectamos el Data Fetcher en RuntimeWiring:
@Configuration
public class GraphQLConfiguration {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(TitleDataFetcher titleDataFetcher) {
return wiring -> wiring
.type("Book", builder -> builder
.dataFetcher("title", titleDataFetcher));
}
}
2. Integración con fuentes remotas (por ejemplo, REST API)
Imagina que author es un microservicio separado que devuelve datos del autor. Ahora crearemos un Data Fetcher para 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 {
// Obtenemos el objeto padre (Book)
Book book = environment.getSource();
// Obtenemos el autor del libro desde otro servicio
ResponseEntity<Author> response = restTemplate.getForEntity(
"http://author-service/authors/" + book.getAuthorId(), Author.class);
return response.getBody();
}
}
3. Procesamiento personalizado de datos
A veces puede que necesites incluir lógica personalizada. Por ejemplo, devolver el campo rating, que se calcula dinámicamente.
@Component
public class RatingDataFetcher implements DataFetcher<Double> {
@Override
public Double get(DataFetchingEnvironment environment) throws Exception {
// Generamos una valoración aleatoria entre 1 y 5
return Math.random() * 5 + 1;
}
}
Casos complejos: cuando los Data Fetchers realmente ayudan
1. Agregación de datos desde varias fuentes
Supongamos que tu consulta GraphQL pide el campo reviews de un libro, que está en otra base de datos. En lugar de tocar el BookResolver principal, puedes crear un Data Fetcher separado para 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 {
// Obtenemos el objeto padre (Book)
Book book = environment.getSource();
return reviewsService.getReviewsForBook(book.getId());
}
}
2. Múltiples Data Fetchers en un mismo campo
A veces puedes querer configurar distintos Data Fetchers según ciertas condiciones. Por ejemplo, el campo price puede obtenerse desde distintas fuentes:
- Si es un libro en stock — desde la base de datos local.
- Si es una copia digital — desde un API externo.
Errores típicos
Trabajando con Data Fetchers, los principiantes suelen cometer estos errores. Por ejemplo, olvidan optimizar las peticiones. Si llamas a 10 Data Fetchers para una sola petición y cada uno hace una consulta a la base de datos, eso puede causar serios problemas de rendimiento. La solución es usar batch loaders (de esto hablaremos más adelante) para minimizar el número de consultas.
También es común olvidar la gestión de excepciones. Si tu Data Fetcher lanza una excepción, puede "tumbar" toda la petición. Usa DataFetcherExceptionHandler para gestionarlo.
En resumen
Los Data Fetchers son una herramienta clave en GraphQL: conectan tu esquema con las fuentes de datos y permiten procesar cada petición con la máxima flexibilidad. Con ellos puedes no solo escalar la aplicación, sino también mejorar significativamente el rendimiento y la flexibilidad, especialmente en una arquitectura de microservicios.
GO TO FULL VERSION