CompletableFuture – це потужний інструмент з бібліотеки Java, який надає зручний інтерфейс для роботи з асинхронними задачами. Його ключові переваги:
- Дозволяє виконувати задачі в окремих потоках, не блокуючи основний потік.
- Підтримує побудову ланцюжків залежних задач.
- Спрощує виконання асинхронних операцій та обробку їх результатів.
У контексті GraphQL CompletableFuture дає нам змогу:
- Обробляти запити паралельно (наприклад, отримувати дані з кількох джерел).
- Не блокувати серверні ресурси в очікуванні відповіді від зовнішніх API.
Крок 1: Підготовка проєкту
Для роботи з CompletableFuture в GraphQL нам знадобиться:
- Додаток Spring Boot з налаштованим Spring GraphQL.
- Створені базові сутності та Data Fetchers у вашому GraphQL проєкті.
Якщо в тебе ще немає проєкту, створи мінімальну структуру:
spring init --dependencies=web,graphql,data-jpa,h2 demo-graphql
Додай залежності в pom.xml (якщо ще не додані):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
Крок 2: Створення схеми для асинхронного GraphQL
У файлі schema.graphqls додамо запит, який поверне список користувачів з інформацією про замовлення:
type User {
id: ID!
name: String!
orders: [Order]!
}
type Order {
id: ID!
description: String!
}
type Query {
users: [User]!
}
Крок 3: Імплементація асинхронних Data Fetchers
1. Симуляція роботи з даними
Для простоти створимо два Service-компонента:
- Один для отримання користувачів (
UserService). - Другий для отримання замовлень (
OrderService).
@Service
public class UserService {
public List<User> getUsers() {
// Симуляція отримання даних
return List.of(
new User(1L, "Alice"),
new User(2L, "Bob")
);
}
}
2. Визначення Data Fetchers
У нашому GraphQL API нам потрібно:
- Повернути список користувачів.
- Для кожного користувача підключити асинхронний виклик для отримання його замовлень.
@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) {
// Асинхронний виклик для отримання замовлень
return orderService.getOrdersForUser(user.getId());
}
}
Що тут відбувається?
- Метод
usersповертає список користувачів. - Для кожного користувача викликається метод
orders, який асинхронно завантажує замовлення для конкретного користувача.
Крок 4: Тестування асинхронності
1. Підключення GraphQL Playground
Додай у application.properties:
spring.graphql.graphiql.enabled=true
spring.graphql.graphiql.path=/graphiql
Тепер ти зможеш тестувати свій GraphQL API через веб-інтерфейс за адресою http://localhost:8080/graphiql.
2. Виконання запиту
Надішли наступний запит:
{
users {
id
name
orders {
id
description
}
}
}
Очікуваний результат:
{
"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" }
]
}
]
}
}
Крок 5: Тестування асинхронного коду
Unit-тести для CompletableFuture
Створи простий тест для перевірки асинхронної поведінки:
@SpringBootTest
class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
void testGetOrdersForUser() throws Exception {
CompletableFuture<List<Order>> future = orderService.getOrdersForUser(1L);
// Блокуємось лише для тестів
List<Order> orders = future.get();
assertEquals(2, orders.size());
assertEquals("Order 1 for user 1", orders.get(0).getDescription());
}
}
Інтеграційні тести для GraphQL
Використай MockMvc для тестування 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());
}
}
Крок 6: Практика та аналіз
Як покращити продуктивність?
- Переконайся, що важкі операції виконуються асинхронно.
- Використовуй
ExecutorServiceдля керування потоками. - Додай кешування, якщо дані рідко змінюються.
Типові помилки:
- Блокування головного потоку. Використовуй
CompletableFutureправильно, щоб уникнути блокувань. - Неправильна обробка виключень. Завжди обробляй
exceptionallyдля обробки помилок. - Не закриті ресурси. Асинхронні операції можуть залишати відкриті з'єднання. Переконайся, що всі ресурси коректно закриті.
return CompletableFuture.supplyAsync(() -> {
// Твоя логіка
}).exceptionally(ex -> {
// Лог помилок
return Collections.emptyList();
});
Тепер твій GraphQL API асинхронний, продуктивний і готовий до взаємодії з зовнішніми API або повільними системами. Можна з гордістю сказати, що ти зробив крок до створення високопродуктивного додатка майбутнього!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ