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:
- Procesar solicitudes en paralelo (por ejemplo, obtener datos de varias fuentes).
- 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:
- Devolver la lista de usuarios.
- 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í?
- El método
usersdevuelve la lista de usuarios. - 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
ExecutorServicepara gestionar los hilos. - Añade caching si los datos cambian raramente.
Errores típicos:
- Bloquear el hilo principal. Usa
CompletableFuturecorrectamente para evitar bloquear. - Manejo incorrecto de excepciones. Siempre maneja
exceptionallypara tratar errores. - 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!
GO TO FULL VERSION