CodeGym /Cursos /Módulo 5. Spring /Lección 295: Práctica: Llamadas asíncronas en GraphQL usa...

Lección 295: Práctica: Llamadas asíncronas en GraphQL usando `CompletableFuture`

Módulo 5. Spring
Nivel 16 , Lección 4
Disponible

CompletableFuture es una herramienta potente de la librería Java que ofrece una interfaz cómoda para trabajar con tareas asíncronas. Sus ventajas clave:

  • Permite ejecutar tareas en hilos separados sin bloquear el hilo principal.
  • Soporta la construcción de cadenas de tareas dependientes.
  • Simplifica la ejecución de operaciones asíncronas y el procesamiento de sus resultados.

En el contexto de GraphQL, CompletableFuture nos da la posibilidad de:

  1. Procesar solicitudes en paralelo (por ejemplo, obtener datos de varias fuentes).
  2. No bloquear los recursos del servidor mientras se espera la respuesta de APIs externas.

Paso 1: Preparar el proyecto

Para trabajar con CompletableFuture en GraphQL necesitaremos:

  • Una aplicación Spring Boot con Spring GraphQL ya configurado.
  • Entidades básicas y Data Fetchers creados en tu proyecto GraphQL.

Si aún no tienes proyecto, crea una estructura mínima:


spring init --dependencies=web,graphql,data-jpa,h2 demo-graphql

Añade las dependencias en pom.xml (si no están añadidas antes):


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>

Paso 2: Crear el esquema para GraphQL asíncrono

En el archivo schema.graphqls añadiremos una consulta que devuelva la lista de usuarios con la información de sus orders:


type User {
    id: ID!
    name: String!
    orders: [Order]!
}

type Order {
    id: ID!
    description: String!
}

type Query {
    users: [User]!
}

Paso 3: Implementación de Data Fetchers asíncronos

1. Simulación de trabajo con datos

Para simplificar vamos a crear dos componentes Service:

  • Uno para obtener usuarios (UserService).
  • Otro para obtener orders (OrderService).

@Service
public class UserService {

    public List<User> getUsers() {
        // Simulación de obtención de datos
        return List.of(
                new User(1L, "Alice"),
                new User(2L, "Bob")
        );
    }
}

2. Definición de los Data Fetchers

En nuestra API GraphQL necesitamos:

  1. Devolver la lista de usuarios.
  2. Para cada usuario, encadenar una llamada asíncrona para obtener sus orders.

@Component
public class GraphQLDataFetcher {

    private final UserService userService;
    private final OrderService orderService;

    public GraphQLDataFetcher(UserService userService, OrderService orderService) {
        this.userService = userService;
        this.orderService = orderService;
    }

    @QueryMapping
    public List<User> users() {
        return userService.getUsers();
    }

    @SchemaMapping(typeName = "User", field = "orders")
    public CompletableFuture<List<Order>> orders(User user) {
        // Llamada asíncrona para obtener los orders
        return orderService.getOrdersForUser(user.getId());
    }
}

¿Qué ocurre aquí?

  1. El método users devuelve la lista de usuarios.
  2. Para cada usuario se llama el método orders, que carga asíncronamente los orders para ese usuario concreto.

Paso 4: Probar la asincronía

1. Conectar GraphQL Playground

Añade en application.properties:


spring.graphql.graphiql.enabled=true
spring.graphql.graphiql.path=/graphiql

Ahora podrás probar tu API GraphQL via la interfaz web en http://localhost:8080/graphiql.

2. Ejecutar la consulta

Envía la siguiente consulta:


{
  users {
    id
    name
    orders {
      id
      description
    }
  }
}

Resultado esperado:


{
  "data": {
    "users": [
      {
        "id": "1",
        "name": "Alice",
        "orders": [
          { "id": "1", "description": "Order 1 for user 1" },
          { "id": "2", "description": "Order 2 for user 1" }
        ]
      },
      {
        "id": "2",
        "name": "Bob",
        "orders": [
          { "id": "1", "description": "Order 1 for user 2" },
          { "id": "2", "description": "Order 2 for user 2" }
        ]
      }
    ]
  }
}

Paso 5: Probar el código asíncrono

Tests unitarios para CompletableFuture

Crea un test sencillo para verificar el comportamiento asíncrono:


@SpringBootTest
class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Test
    void testGetOrdersForUser() throws Exception {
        CompletableFuture<List<Order>> future = orderService.getOrdersForUser(1L);

        // Comprobamos que los datos han vuelto correctamente
        List<Order> orders = future.get(); // Bloqueamos solo en tests
        assertEquals(2, orders.size());
        assertEquals("Order 1 for user 1", orders.get(0).getDescription());
    }
}

Tests de integración para GraphQL

Usa MockMvc para testear las consultas GraphQL:


@WebMvcTest(GraphQLDataFetcher.class)
class GraphQLDataFetcherTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testUsersQuery() throws Exception {
        String query = "{ users { id, name, orders { id, description } } }";
        mockMvc.perform(post("/graphql")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"query\": \"" + query + "\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.users").isArray());
    }
}

Paso 6: Práctica y análisis

¿Cómo mejorar el rendimiento?

  • Asegúrate de que las operaciones pesadas se ejecuten de forma asíncrona.
  • Usa ExecutorService para gestionar los hilos.
  • Añade caching si los datos cambian raramente.

Errores típicos:

  1. Bloquear el hilo principal. Usa CompletableFuture correctamente para evitar bloquear.
  2. Manejo incorrecto de excepciones. Siempre maneja exceptionally para tratar errores.
  3. Recursos sin cerrar. Las operaciones asíncronas pueden dejar conexiones abiertas. Asegúrate de cerrar todos los recursos correctamente.
return CompletableFuture.supplyAsync(() -> {
    // Tu lógica aquí
}).exceptionally(ex -> {
    // Registro de errores
    return Collections.emptyList();
});

Ahora tu API GraphQL es asíncrono, eficiente y está listo para interactuar con APIs externas o sistemas lentos. Puedes decir con orgullo que has dado un paso hacia la creación de una aplicación de alto rendimiento del futuro!

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION