Compress me
Strawberry Shake removes the complexity of state management and lets you interact with local and remote data through GraphQL.
На деле выгодно отличается от всего остального тем что:
- валидирует запросы - читай не возможно очепятаться в запросе
- использует кодо генерацию для результатов - читай не возможно забыть что в запросе убрали поле, а из модельки забыли
- генерит интерфейсы для всего на свете - читай проще при тестировании
Демо проект:
mkdir demo
cd demo
dotnet new web
dotnet add package StrawberryShake.Transport.Http
dotnet add package StrawberryShake.CodeGeneration.CSharp.AnalyzersЕдиножды:
dotnet tool install StrawberryShake.Tools --global
dotnet graphql init https://dracula.rabota.ua/graphql -n DraculaClientЭта команда стянет схему и создаст конфиг .graphqlrc.json в котором есть смысл выключить emitGeneratedCode (что бы не создавалась папка Generated, все сгенерированные файлы все равно будут доступны, плюс что бы не сводить с ума IDE), а так же по желанию включить генерацию record за вместо class для inputs и entities.
Теперь где угодно в проекте создаем graphql файл с каким либо запросом, например:
GetPublishedVacancies.graphql
query GetPublishedVacancies($keywords: String!) {
publishedVacancies(
filter: { keywords: $keywords }
pagination: { count: 3 }
) {
items {
id
title
}
}
}Примечания:
- vscode и rider имеют graphql плагины, благодаря чему не только смогут подсветить синтаксис и подсказать что писать дальше, но и запустить запрос прямо из редактора
- благодаря валидации, если я очепятаюсь где либо и например напишу
tittleто проект просто напросто не собереться с ошибкойGetPublishedVacancies.graphql(6, 8): [SS0002] The field `tittle` does not exist on the type `Vacancy`.
Собираем проект dotnet build (первый раз, возможно понадобиться перезапустить редактор, что бы последний расчехлился и “увидел” сгенерированный код)
Дальше регистрируем наш клиент:
builder.Services
.AddDraculaClient()
.ConfigureHttpClient(client => client.BaseAddress = new Uri("https://dracula.rabota.ua"));Ну и по месту инжектим IDraculaClient и вызываем сгенерированный GetPublishedVacancies, напр:
var client = app.Services.GetRequiredService<IDraculaClient>();
var vacancies = await client.GetPublishedVacancies.ExecuteAsync("PHP");
if (vacancies.Data != null && vacancies.Data?.PublishedVacancies.Items != null)
{
foreach (var vacancy in vacancies.Data.PublishedVacancies.Items)
{
Console.WriteLine($"{vacancy?.Id}: {vacancy?.Title}");
}
}Примечание: блин, у нас тут на выходе
[Vacancy]из-за чего все все nullable 🤷♂️
или:
app.MapGet("/vacancies", ([FromServices] IDraculaClient client) => client.GetPublishedVacancies.ExecuteAsync("PHP"));Profit
Вопросы
Как обновить схему
dotnet graphql updateНужно ли коммитать схему
Да, без нее проект не собереться
Тут кстати тоже любопытно, получается в процессе сборки благодаря этом можно дополнительно проверять, все еще актуальная схема или нет в проектче (читай обеспечивать совместимость)
Плагин rider хочет создать второй конфиг
Действительно, в rider используется “старый” подход с .graphqlconfig но для нас это совсем не проблема, так как он смотрит на тот же файл схемы
Как прокинуть токен пользователя
Что то типа такого (псевдо):
builder.Services
.AddHttpContextAccessor()
.AddDraculaClient()
.ConfigureHttpClient((provider, client) =>
{
client.BaseAddress = new Uri("https://dracula.rabota.ua");
client.Timeout = TimeSpan.FromSeconds(5);
// var ctx = provider.GetRequiredService<IHttpContextAccessor>();
// client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ctx.HttpContext?.Request.Headers.Authorization)
// client.DefaultRequestHeaders.AcceptLanguage = ctx.HttpContext.Request.Headers.AcceptLanguage;
});Подробнее расписанно в доке, конфигурация вызывается на каждый запрос
Как прокинуть токен пользователя из консольного консьюмера
На примере Accep Language, но с Authorization будет работать так же
Предположим у нас есть следующий запрос:
query GetCity($id: ID!) {
city(id: $id) {
name
}
}И пример приложения которое утилизирует фичу языка AsyncLocal
using demo;
using StrawberryShake;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddDraculaClient()
.ConfigureHttpClient((serviceProvider, client) =>
{
client.BaseAddress = new Uri("https://dracula.rabota.ua");
if (LangOperation.Value != null)
{
client.DefaultRequestHeaders.Add("Accept-Language", LangOperation.Value);
}
});
var app = builder.Build();
var client = app.Services.GetRequiredService<IDraculaClient>();
var defaultBehavior = await client.GetCity.ExecuteAsync("1");
Console.WriteLine(defaultBehavior.Data.City.Name); // Киев
var acceptedLanguage = await LangOperation.Run("en", token => client.GetCity.ExecuteAsync("1", token));
Console.WriteLine(acceptedLanguage.Data.City.Name); // Kiev
var t1 = Task.Run(async () =>
{
var client = app.Services.GetRequiredService<IDraculaClient>();
var result = await LangOperation.Run("en", token => client.GetCity.ExecuteAsync("1", token));
Console.WriteLine("t1(en): " + result.Data.City.Name); // t2(uk): Kiev
});
var t2 = Task.Run(async () =>
{
var client = app.Services.GetRequiredService<IDraculaClient>();
var result = await LangOperation.Run("uk", token => client.GetCity.ExecuteAsync("1", token));
Console.WriteLine("t2(uk): " + result.Data.City.Name); // t2(uk): Київ
});
Task.WaitAll(t1, t2);
public static class LangOperation
{
private static readonly AsyncLocal<string> Store = new();
public static string? Value => Store.Value;
public static async Task<IOperationResult<TResultData>> Run<TResultData>(string language, Func<CancellationToken, Task<IOperationResult<TResultData>>> implementation) where TResultData : class
{
Store.Value = language;
return await implementation(CancellationToken.None);
}
}Проверяем поведение по умолчанию, затем эту штуку с передачей заголовка и затем в нескольких потоках.
Все это дело было найденно вот тут
С Аней столкнулись со следующей проблемой, конретно в Windows и в VisualStudio, как то ну очень странно себя все это дело ведет, если генерируемый код не виден и ничего не помогает - есть смысл включить emit генерируемых файлов, что бы они прям в папке появились