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 или медленными системами. С гордостью можно сказать, что вы сделали шаг к созданию высокопроизводительного приложения будущего!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ