Ресурсы
РесурсыЛучшие практики GraphQL

Лучшие практики GraphQL

GraphQL достаточно зрелый инструмент, и он существует достаточно долго, чтобы сообщество опубликовало множество статей, делящихся лучшими практиками. Эти руководства охватывают практически все аспекты GraphQL, включая проектирование схемы, соглашения об именовании, обеспечение безопасности и предоставление информативных сообщений об ошибках.

Это одни из самых убедительных руководств по лучшим практикам в GraphQL.

Лучшие практики на graphql.org

Официальный сайт GraphQL предлагает общее введение в лучшие практики GraphQL.

Эти материалы охватывают преимущественно высокоуровневые вопросы, такие как:

Где располагается слой GraphQL в архитектуре

Рекомендации Ли Байрона

Вскоре после выпуска GraphQL в открытый доступ создатель GraphQL Ли Байрон опубликовал статью Lessons From 4 Years of GraphQL, описывая, как следует концептуально подходить к работе с GraphQL:

  • Именование имеет значение
  • Думайте в терминах графов, а не конечных точек
  • Описывайте данные, а не представление
  • GraphQL — это тонкий интерфейс
  • Скрывайте детали реализации

Он также подробно описывает несколько ценных принципов и уроков, извлечённых им в процессе использования GraphQL в Facebook.

GraphQL Rules

GraphQL Rules — сайт, специально посвящённый повседневным лучшим практикам работы с GraphQL, преимущественно касающимся проектирования схемы GraphQL.

Этот ресурс весьма основателен. Он объединяет информацию из нескольких исключительных источников (таких как статья Designing GraphQL Mutations и руководство Shopify Designing a GraphQL API) и представляет их вместе в сжатой форме.

Описанные правила включают:

  1. Правила именования
    • Используйте camelCase для полей и аргументов GraphQL.
    • Используйте UpperCamelCase для типов GraphQL.
    • Используйте CAPITALIZED_WITH_UNDERSCORES для именования типов ENUM.
  2. Правила типов
    • Используйте пользовательские скалярные типы, если хотите объявить поля или аргументы с конкретным семантическим значением.
    • Используйте Enum для полей, которые содержат фиксированный набор значений.
  3. Правила полей (Output)
    • Используйте семантические имена для полей и избегайте утечки деталей реализации в названиях полей.
    • Используйте поле Non-Null, если поле всегда будет иметь значение.
    • По возможности группируйте связанные поля в пользовательский Object type.
  4. Правила аргументов (Input)
    • Группируйте связанные аргументы в новый input-type.
    • Используйте строгие скалярные типы для аргументов, например DateTime вместо String.
    • Помечайте аргументы как required, если они необходимы для выполнения запроса.
  5. Правила списков
    • Для фильтрации списков используйте аргумент filter, содержащий все доступные фильтры.
    • Используйте аргумент sort типа Enum или [Enum!] для сортировки списков.
    • Используйте limit со значением по умолчанию и skip для ограничения количества возвращаемых элементов в списке.
    • Используйте аргументы page, perPage для пагинации и возвращайте output type с items (массив элементов) и pageInfo (метаданные).
    • Для бесконечных списков (infinite scroll) используйте Relay Cursor Connections Specification.
  6. Правила мутаций
    • Используйте Namespace-types для группировки мутаций в рамках одного ресурса.
    • Выходите за рамки CRUD — создавайте небольшие мутации для различных бизнес-операций над ресурсами.
    • Рассматривайте возможность выполнения мутаций над несколькими элементами (пакетные изменения одного типа).
    • Мутации должны чётко описывать все обязательные аргументы; не должно быть вариантов «или/или».
    • В мутациях помещайте все переменные в один уникальный аргумент input.
    • Каждая мутация должна иметь уникальный payload type.

Лучшие практики для resolvers

Статья GraphQL Resolvers: Best Practices описывает, как лучше создавать field resolvers. Несмотря на то что она ориентирована на серверы Node.js (инфраструктура PayPal основана на Express), многие её уроки применимы и к другим технологиям, включая PHP.

Основные выводы:

  • Получение и передача данных от родителя к потомку должны использоваться умеренно.
  • Используйте библиотеки, такие как dataloader, для устранения дублирующихся запросов к нижестоящим сервисам.
  • Осознавайте нагрузку, которую вы создаёте на свои источники данных.
  • Не мутируйте «context». Это обеспечивает согласованный и менее подверженный ошибкам код.
  • Пишите resolvers, которые удобочитаемы, поддерживаемы и тестируемы. Без излишней хитрости.
  • Делайте resolvers как можно более компактными. Выносите логику получения данных в переиспользуемые асинхронные функции.

OWASP - GraphQL Cheat Sheet

OWASP (Open Web Application Security Project) — некоммерческий фонд, работающий над повышением безопасности программного обеспечения. Он исследует уязвимости различных технологий к эксплойтам и детально описывает решения проблем безопасности, упрощая разработчикам предотвращение атак.

OWASP опубликовал GraphQL Cheat Sheet, объясняющий, какие атаки наиболее распространены и какие проблемы безопасности наиболее значительны в GraphQL, а также как их устранять.

Распространённые атаки на GraphQL:

  1. Injection — как правило, включает, но не ограничивается:
    • SQL и NoSQL injection
    • OS Command injection
    • SSRF и CRLF injection/Request Smuggling
  2. DoS (Denial of Service)
  3. Злоупотребление нарушенной авторизацией: ненадлежащий или избыточный доступ, включая IDOR
  4. Batching Attacks — специфический для GraphQL метод атаки грубой силой
  5. Злоупотребление небезопасными конфигурациями по умолчанию

OWASP также предоставляет рекомендации по предотвращению каждой из этих угроз.

Лучшие практики с queries GraphQL

Команда Apollo опубликовала GraphQL query best practices, давая практические советы по составлению GraphQL queries.

Например, эти два queries достигают одной цели, но поскольку первый имеет имя операции, он более понятен и полезен при отладке:

# Recommended ✅
query GetBooks {
  books {
    title
  }
}
 
# Not recommended ❌
query {
  books {
    title
  }
}

Их рекомендации включают:

  • Называйте все операции
  • Используйте переменные для передачи аргументов GraphQL
  • Запрашивайте только те данные, которые вам нужны, там, где они нужны
  • Используйте fragments для инкапсуляции связанных наборов полей
  • Запрашивайте глобальные данные и данные, специфичные для пользователя, отдельно

Использование единого графа

Также от команды Apollo — сайт Principled GraphQL, который объясняет, что GraphQL — это не только спецификация, но и, возможно, что важнее, интерфейс для взаимодействия с «графом» данных нашей компании.

Через список из 10 принципов этот сайт описывает, как извлечь максимум из единого графа:

  1. One Graph: ваша компания должна иметь единый унифицированный граф, а не множество графов, создаваемых каждой командой.
  2. Federated Implementation: хотя граф один, его реализация должна быть федерирована между несколькими командами.
  3. Track the Schema in a Registry: должен быть единый источник истины для регистрации и отслеживания графа.
  4. Abstract, Demand-Oriented Schema: схема должна выступать в роли уровня абстракции, обеспечивающего гибкость потребителям при скрытии деталей реализации сервиса.
  5. Use an Agile Approach to Schema Development: схема должна создаваться инкрементально на основе реальных требований и плавно развиваться со временем.
  6. Iteratively Improve Performance: управление производительностью должно быть непрерывным процессом, основанным на данных, плавно адаптирующимся к меняющимся нагрузкам queries и реализациям сервисов.
  7. Use Graph Metadata to Empower Developers: разработчики должны иметь глубокое понимание графа на протяжении всего процесса разработки.
  8. Access and Demand Control: предоставляйте доступ к графу на основе каждого клиента и управляйте тем, к чему и как клиенты могут получать доступ.
  9. Structured Logging: фиксируйте структурированные журналы всех операций с графом и используйте их как основной инструмент для понимания использования графа.
  10. Separate the GraphQL Layer from the Service Layer: применяйте многоуровневую архитектуру, где функциональность графа выделена в отдельный уровень, а не встроена в каждый сервис.