ZeroQL - C# GraphQL 클라이언트는 조각 지원을 추가합니다.

26181 단어 csharpgraphql
이전에는 ZeroQL을 소개했습니다.
성능 저하 없이 Linq와 유사한 인터페이스를 갖춘 C# "네이티브"graphql 클라이언트입니다.
이 기사에서는 프래그먼트에 대한 지원을 제시하고자 합니다.

파편은 무엇을 의미합니까?



graphql에서 다음과 같이 서버에서 가져오려는 모든 필드를 지정해야 합니다.

query GetMe {
    me {
        id
        firstName
        lastName
    }
}


한 번만 수행해야 하는 경우에는 괜찮지만 경우에 따라 동일한 필드 집합을 반복해서 가져와야 할 수 있습니다. 너무 장황해질 수 있습니다.

query GetMeAndFriend($friendId: Int!) {
    me {
        id
        firstName
        lastName
    }
    user(id: $friendId) {
        id
        firstName
        lastName
    }
}


삶을 더 쉽게 만들기 위해 GraphQL에는 지원 프래그먼트가 있습니다. 필드 세트를 정의한 다음 필요할 때 재사용할 수 있습니다.

fragment UserFields on User {
    id
    firstName
    lastName
}

query GetMeAndFriend($friendId: Int!) {
    me {
        ...UserFields
    }
    user(id: $friendId) {
        ...UserFields
    }
}


나는 아이디어가 분명하다고 생각합니다. 이제 C#에서 프래그먼트를 생성하는 방법을 살펴보겠습니다.

우리는 다음 GraphQL 스키마로 작업할 것입니다.

schema {
  query: Query
  mutation: Mutation
}

type Query {
  me: User!
  user(id: Int!): User
}

type Mutation {
  addUser(firstName: String!, lastName: String!): User!
}

type User {
  id: Int!
  firstName: String!
  lastName: String!
  role: Role!
}

type Role {
  id: Int!
  name: String!
}


How to setup the ZeroQL you can find in .



위 샘플의 GetMeAndFriend GraphQL 쿼리는 다음과 같습니다.

var variables = new { FriendId = 2 };
var response = await client.Query(
    variables, 
    static (i, q) => new
    {
        Me = q.Me(o => new { o.Id, o.FirstName, o.LastName }),
        User = q.User(i.FriendId, o => new { o.Id, o.FirstName, o.LastName }),
    });

Console.WriteLine(response.Query); // query ($friendId: Int!) { me { id firstName lastName }  user(id: $friendId) { id firstName lastName } }
Console.WriteLine(response.Data); //  { Me = { Id = 1, FirstName = Jon, LastName = Smith }, User = { Id = 2, FirstName = Ben, LastName = Smith } }


사용자 필드를 프래그먼트로 이동해 보겠습니다. 이를 위해서는 사용자 모델과 확장 메서드가 필요합니다.

public record UserModel
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public static class UserFragments
{
    [GraphQLFragment]
    public static UserModel AsUserModel(this User user)
    {
        return new UserModel
        {
            Id = user.Id,
            FirstName = user.FirstName,
            LastName = user.LastName
        };
    }
}


이제 쿼리를 다음과 같이 다시 작성할 수 있습니다.

var variables = new { FriendId = 2 };
var response = await client.Query(
    variables, 
    static (i, q) => new
    {
        Me = q.Me(o => o.AsUserModel()),
        User = q.User(i.FriendId, o => o.AsUserModel()),
    });

Console.WriteLine(response.Query); // query ($friendId: Int!) { me { id firstName lastName }  user(id: $friendId) { id firstName lastName } }
Console.WriteLine(response.Data); // { Me = UserModel { Id = 1, FirstName = Jon, LastName = Smith }, User = UserModel { Id = 2, FirstName = Ben, LastName = Smith } }


그리고 모든 것이 예상대로 작동합니다. 생성된 GraphQL 쿼리를 보면 엄밀히 말하면 "graphql 조각"이 아님을 알 수 있습니다. 최종 쿼리에 삽입되는 하위 쿼리에 가깝습니다. 따라서 더 나아가 다음과 같이 여러 쿼리를 결합할 수 있습니다.

var variables = new { FriendId = 2 };
var response = await client.Query(
    variables, 
    static (i, q) => q.GetMeAndFriend(i.FriendId));

Console.WriteLine(response.Query); // query ($friendId: Int!) { me { id firstName lastName }  user(id: $friendId) { id firstName lastName } }
Console.WriteLine(response.Data); // MeAndFriendResponse { Me = UserModel { Id = 1, FirstName = Jon, LastName = Smith }, Friend = UserModel { Id = 2, FirstName = Ben, LastName = Smith } }

// ...

public record MeAndFriendResponse
{
    public UserModel Me { get; set; }
    public UserModel Friend { get; set; }
}

public static class QueryFragments
{
    [GraphQLFragment]
    public static MeAndFriendResponse GetMeAndFriend(this Query query, int friendId)
    {
        return new MeAndFriendResponse
        {
            Me = query.Me(o => o.AsUserModel()),
            Friend = query.User(friendId, o => o.AsUserModel())
        };
    }
}


다시 말하지만 모든 것이 예상대로 작동합니다.

제한 사항



삶을 약간 복잡하게 만들 수 있는 한 가지가 있습니다. 소스 생성기는 소스 코드를 살펴보고 쿼리를 생성합니다. 또한 모든 하위 쿼리를 조사해야 하며 다른 어셈블리/프로젝트에서 정의될 때까지 괜찮습니다. 이 경우 해당 어셈블리의 소스 코드를 사용할 수 없기 때문에 소스 생성기가 파헤칠 수 없습니다. 결과적으로 쿼리를 생성할 수 없습니다. 프래그먼트가 작동하도록 하려면 client.Query 또는 client.Mutation 메서드라고 하는 동일한 어셈블리에서 정의해야 합니다. 이 요구 사항은 확실히 일부 워크플로우와 기대치를 깨뜨릴 수 있습니다. 동시에, 얼마나 자주 SQL 쿼리를 다른 어셈블리로 분할해야 합니까? 이것은 발생하지만 그렇게 자주 발생하지는 않습니다. 최악의 시나리오에서는 한 프로젝트에서 다른 프로젝트로 조각을 복사하여 붙여넣어야 합니다.

다시 말하지만 이 문제가 향후에 해결될 수 없다는 의미는 아닙니다. 해결 방법에 대한 몇 가지 아이디어가 있지만 현재로서는 구현이 까다로운 아이디어 일뿐입니다. 한편 싸움은 끝나지 않았습니다.

성능



나는 ZeroQL이 뛰어난 성능을 가지고 있다고 말했습니다. 그러나 얼마나 뛰어난가?
저장소에는 benchmark이 포함되어 있습니다. 원시 graphql 요청, StrawberryShake 및 ZeroQL을 비교합니다.

짧은 버전은 다음과 같습니다.

[Benchmark]
public async Task<string> Raw()
{
    var rawQuery = @"{ ""query"": ""query { me { firstName }}"" }";
    var response = await httpClient.PostAsync("", new StringContent(rawQuery, Encoding.UTF8, "application/json"));
    var responseJson = await response.Content.ReadAsStreamAsync();
    var qlResponse = JsonSerializer.Deserialize<JsonObject>(responseJson, options);

    return qlResponse["data"]["me"]["firstName"].GetValue<string>();
}

[Benchmark]
public async Task<string> StrawberryShake()
{
    var firstname = await strawberryShake.Me.ExecuteAsync(); // query { me { firstName }}
    return firstname.Data.Me.FirstName;
}

[Benchmark]
public async Task<string> ZeroQL()
{
    var firstname = await zeroQLClient.Query(static q => q.Me(o => o.FirstName));

    return firstname.Data;
}


결과는 다음과 같습니다.


BenchmarkDotNet=v0.13.1, OS=macOS Monterey 12.4 (21F79) [Darwin 21.5.0]
Apple M1, 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.302
  [Host]     : .NET 6.0.7 (6.0.722.32202), Arm64 RyuJIT
  DefaultJob : .NET 6.0.7 (6.0.722.32202), Arm64 RyuJIT





방법
평균
오류
표준 편차
0세대
할당됨


날것의
182.5μs
1.07μs
1.00μs
2.4414
5KB

딸기쉐이크
190.9μs
0.74μs
0.69μs
3.1738
6KB

ZeroQL
185.9μs
1.39μs
1.30μs
2.9297
6KB


보시다시피 Raw 방법이 가장 빠릅니다.ZeroQL 방법은 StrawberryShake 방법보다 약간 빠릅니다.
그러나 절대적인 관점에서 보면 모두 거의 동일합니다. 그 차이는 미미합니다.

요약



따라서 ZeroQL를 사용하면 graphql은 잊어버리고 완전한 유형의 Linq와 유사한 인터페이스를 사용할 수 있습니다. 성능에 거의 영향을 미치지 않습니다. 앞으로는 다른 어셈블리에 정의된 조각에 대한 쿼리를 생성하는 방법을 파악하고 초기 설정을 보다 간단하게 만드는 방법에 대해 생각할 계획입니다.

시간 내 주셔서 감사합니다!
내가 하고 있는 일이 마음에 든다면. 더 많은 사람들이 볼 수 있도록 Github 저장소를 시작하십시오.

연결



Github
NuGet

좋은 웹페이지 즐겨찾기