JavaRush /Курси /Модуль 5. Spring /Лекція 300: Розбір типових помилок при розробці GraphQL A...

Лекція 300: Розбір типових помилок при розробці GraphQL API

Модуль 5. Spring
Рівень 16 , Лекція 9
Відкрита

GraphQL — потужний інструмент для побудови API, але, як відомо, з великою потужністю приходить велика відповідальність. Навіть найдосвідченіші розробники припускаються помилок при проєктуванні GraphQL-схем, обробників даних і запитів. У цій лекції розберемо найпоширеніші помилки, дізнаємося, як їх уникнути, і подивимось кращі практики для написання якісного та продуктивного GraphQL API.


Типові помилки при проєктуванні схеми

Неповна або надмірна схема

Проєктування схеми — це певне мистецтво, і тут легко або перестаратися, або навпаки створити схему, яка не покриває потрібний функціонал.

Приклад помилки:


type Query {
  userProfile: UserProfile
}

type UserProfile {
  id: ID
  firstName: String
  lastName: String
  age: Int
  hobbies: [String]
  contactInfo: String
}

Здається нормально, але що якщо contactInfo містить чутливі дані? Така схема може легко призвести до витоку.

Як уникнути:

  • Проєктуйте мінімалістичні та ізольовані схеми.
  • Використовуйте підхід need-to-know при наданні даних і уникайте «перегодовування» клієнтів інформацією.

Приклад виправленої схеми:


type Query {
  userProfile: UserProfile
}

type UserProfile {
  id: ID
  firstName: String
  lastName: String
  age: Int
  hobbies: [String]
}

type PrivateUserInfo {
  contactInfo: String
}

Тепер конфіденційна інформація винесена в окремий тип, який можна надавати лише авторизованим користувачам.

Глибокі вкладені структури (проблема N+1)

Помилка:

Якщо ваша схема містить занадто глибокі вкладення, клієнт може запитувати величезні обсяги даних одним потужним запитом.

Приклад:


query {
  users {
    id
    posts {
      id
      comments {
        id
        text
      }
    }
  }
}

Якщо є тисячі користувачів, у кожного десятки постів, а у кожного поста сотні коментарів, цей запит може «розвалити» ваш сервер.

Як уникнути:

  • Обмежуйте глибину запитів через плагіни або middleware.
  • Використовуйте Batch Loading для усунення дублюючих запитів.

Помилки в обробці даних

Проблеми з продуктивністю (помилка N+1)

Проблема: Поширена помилка при використанні GraphQL пов'язана з тим, що при витяганні пов'язаних даних запити до бази даних починають «вистрілювати» як з кулемета.

Приклад:


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

@QueryMapping
public List<Post> posts(@Argument("userId") String userId) {
    return postService.getPostsByUser(userId);
}

Кожен користувач може ініціювати окремий запит для отримання пов'язаних постів. Якщо користувачів 100, то буде виконано 100 SQL-запитів до бази даних.

Виправлення: Використовуємо DataLoader:


@Bean
public DataLoaderRegistry dataLoaderRegistry() {
    DataLoaderRegistry registry = new DataLoaderRegistry();

    DataLoader<String, List<Post>> postLoader = DataLoader.newMappedDataLoader(userService::getPostsForUsers);
    registry.register("posts", postLoader);

    return registry;
}

Тепер для 100 користувачів буде виконано всього 1 батч-запит.

Необроблені виключення

Помилка: ваш обробник даних кидає необроблені виключення, які потрапляють у серверні логи і потенційно розкривають інформацію про внутрішню структуру системи.

Приклад:

@QueryMapping
public User user(@Argument("id") String id) {
    return userService.findById(id);
}

Якщо користувач з вказаним id не знайдений, скоріше за все, буде викинуто NullPointerException.

Рішення: Огорніть помилки в кастомні виключення.


@QueryMapping
public User user(@Argument("id") String id) {
    return userService.findById(id).orElseThrow(() -> new CustomException("User not found"));
}

Помилки захисту даних

Відсутність обмежень по глибині та складності запитів

GraphQL дає клієнту таку гнучкість, що недобросовісні користувачі можуть відправити запити з глибиною в 1000 рівнів або зі складністю, що перевищує ліміти в 100 тисяч операцій.

Рішення:

  • Увімкніть обмеження глибини і складності запитів.
  • Використовуйте бібліотеки такі як graphql-java або плагіни для налаштування лімітів.

GraphQLSchema schema = GraphQLSchema.newSchema()
    .query(QueryType)
    .maxQueryDepth(10)
    .maxQueryComplexity(1000)
    .build();

Відсутність перевірки авторизації та аутентифікації

Помилка: Часто забувають про перевірку авторизації користувачів при виконанні запитів.

Рішення:

Використовуйте DataFetchEnvironment, щоб валідовувати поточного користувача:


public CompletableFuture<User> user(DataFetchingEnvironment env) {
    String token = env.getContext();
    if (!authService.isAuthenticated(token)) {
        throw new AuthenticationException("Invalid token");
    }
    return userService.getUserByToken(token);
}

Помилки в тестуванні

Непокриття тестами складних запитів

Багато хто обмежується тестами для простих запитів, але забуває тестувати запити з фрагментами, вкладеними полями і мутаціями.

Рішення:

Пишіть тести для всіх сценаріїв використання. Наприклад, використовуйте MockMvc для інтеграційних тестів:


mockMvc.perform(post("/graphql")
        .content("{\"query\":\"{ user { id, name } }\"}")
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.data.user.id").exists());

Помилки у проєктуванні API

Дублювання логіки в схемах і резолверах

Помилка: Логіка повторюється для однієї і тієї ж поведінки в GraphQL API.

Рішення:

Використовуйте сервісний шар для обробки бізнес-логіки і підлаштовуйте її під контекст запиту в резолверах.


Практика: виправлення помилок

На завершення лекції розглянемо вправи:

  1. Виправте сценарій з проблемою N+1 з використанням DataLoader.
  2. Реалізуйте обмеження глибини запитів.
  3. Напишіть інтеграційні тести для мутації з вкладеними об'єктами.

На реальному проєкті це дасть вам більш стійке API, а клієнти будуть вдячні.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ