GraphQL — это уже не модно, а популярно. И он продолжает становиться всё более популярной технологией для построения веб-серверов.
В .net всё ещё нет хорошего клиента для GraphQL, который позволял бы строить типизированные запросы с помощью средств языка, а не писать сырые запросы GraphQL.
Ну, то есть, есть что-то. Например, Strawberry Shake требует, чтобы вы написали сырой GraphQL, но создает все необходимые обертки для С# — так что в итоге получается типобезопасный API. Но хочется чего-то более нативного, эдакое обобщенное АПИ чтобы можно было задавать запросы подобным образом:
ZeroQL
ZeroQL — это клиент GraphQL на языке C# с Linq-подобным интерфейсом и отличной производительностью, эквивалентной простому HTTP-вызову.
Давайте посмотрим, как он работает. Пусть у нас есть GraphQL-сервер на адресе http://localhost:10000/ с следующей схемой данных:
Для начала создадим тестоый проект и настроим его для работы с нашим сервером — для этого нужно подключить нагет-пакет ZeroQL, получить схему с сервера GraphQL, установить утилиту ZeroQL.CLI и сгенерировать с её помощью типизированные обертки для схемы:
Для проекта в активной разработке можно добавить шаги обновления схемы и генерации оберток как часть процесса сборки проекта.
Отлично. Сразу после этого можно отправить первый запрос на GraphQL-сервер:
Как это работает
Класс MyServerGraphQLClient создается с помощью ZeroQL.CLI. В нем есть метод Query, который принимает делегат Func<TQuery, TResult> query, нужные типы запросов и результатов для делегата генерируются по схеме GraphQL-сервера.
Source generator ищет использования метода Query, анализирует запрос в делегате, преобразует его в соответствующий GraphQL и складывает соответствия делагата (в виде строки) и GraphQL-запроса в словарь.
Чтобы достать значение запроса в виде строки в методе Query используется скрытый аргумент queryKey с атрибутом CallerArgumentExpression:
Атрибут CallerArgumentExpressionAttribute появился в C# 10. Он позволяет нам получить строковое представление выражения, которое было передано внутри аргумента. В нашем случае мы ищем аргумент query, который равен static q => q.Me(o => new { o.Id, o.FirstName, o.LastName }), в результате аргумент queryKey будет содержать строку "static q => q.Me(o => new { o.Id, o.FirstName, o.LastName })".
В результате мы всегда знаем, какой graphql-запрос нам нужен для каждого вызова. Важным моментом здесь является то, что сами graphql-запросы генерируется во время компиляции. Поэтому по факту мы имеем нулевые накладные расходы — парсить выражение во время выполнения не нужно, а запрос будет извлечен из словаря по ключу строкового представления выражения.
Важный момент из примера выше — выражение в делегате для запроса должно быть статическим (static). На это есть две причины. Во-первых, анализировать ее с помощью source generator гораздо проще, потому что нет переменных, выходящих за пределы области видимости, которые могут все усложнить. Во-вторых, если вы планируете параметризовать запрос:
, то это самый простой способ убедиться, что все параметры учтены — source generator сереализует их и добавит в запрос.
Поддерживаемые фичи
Во-первых, вложенные поля!
Во-вторых, возврат нескольких значений из разных запросов:
Именованные запросы:
Мутации данных:
Ограничения
Из-за атрибута CallerArgumentExpressionAttribute библиотека доступна только для .NET 6. Сейчас в разработке находятся:
- фрагменты
- подписки
- @defer
- @stream
Тут стоит сказать, что библиотека очень новая и находится в активной разработке. Можно порадовать автора звездой на Гитхабе.
Оригинальный анонс на dev.to.