🦀

RustのSchema First GraphQLライブラリrusty-gqlを作りました

2022/01/31に公開
1

rusty-gql

Schema FirstのRust製GraphQLライブラリであるrusty-gqlを作りました。この記事ではその紹介(宣伝)をさせていただきます。

なぜ作ったのか

これまでRustのGraphQLライブラリはjuniperasync-graphqlがありました。

これら2つはCode Firstで設計されており、GraphQLのスキーマ定義をRustのマクロを使用して定義します。

以下はasync-graphqlの例です。

async_graphql.rs
use async_graphql::*;

struct MyObject {
    value: i32,
}

#[Object]
impl MyObject {
    async fn value(&self) -> String {
        self.value.to_string()
    }

    async fn value_from_db(
        &self,
        ctx: &Context<'_>,
        #[graphql(desc = "Id of object")] id: i64
    ) -> Result<String> {
        let conn = ctx.data::<DbPool>()?.take();
        Ok(conn.query_something(id)?.name)
    }
}

#[derive(Interface)]
#[graphql(
    field(name = "area", type = "f32"),
    field(name = "scale", type = "Shape", arg(name = "s", type = "f32")),
    field(name = "short_description", method = "short_description", type = "String")
)]
enum Shape {
    Circle(Circle),
    Square(Square),
}

私個人の意見ですが、GraphQLのサーバー実装はSchema Firstの方が適していると考えています。
特にそれがクライアントサイドのエンジニアにとってあまり馴染みがない言語である場合において。

GraphQLはUIに応じたtree構造でデータを返却できることが特徴です。つまり、クライアントサイドエンジニアのためのものなので、フロントエンドやモバイルエンジニアの方もスキーマ定義を簡単に把握できることが重要と考えています。

Code Firstである場合、GraphQLのスキーマ定義を使用せずにサーバー実装の言語でスキーマを定義します。そのため、サーバサイドの実装言語に馴染みのない方にとっては把握しづらいものとなります。
Schema Firstの場合は最初に.graphqlファイルでスキーマを定義するため、クライアントサイド、バックエンドのエンジニア双方が理解したフォーマットで仕様を決めることができます。
開発プロセスによってはUIを作るエンジニアが.graphqlファイルを編集してPRを作成し、それに応じてサーバーサイドのエンジニアが実装するということも可能になります。

ちなみに余談ではありますが、昨年発表されたNetflixのGraphQLライブラリであるDGSもSchema Firstを推奨しています。

Open Sourcing the Netflix Domain Graph Service Framework: GraphQL for Spring Boot

Getting Started

プロジェクトの作成

rusty-gqlのプロジェクトを作成するためにrusty-gql-cliをインストールします。

cargo install rusty-gql-cli

rusty-gqlコマンドを使用してプロジェクトを作成します。

rusty-gql new gql-example
cd gql-example

あとはcargo runでサーバーを起動するだけです。

GraphQLのスキーマ定義

最初に述べたようにrusty-gqlはSchema Firstで開発することを前提に作成しているので、まずはスキーマを定義します。
schema/**配下のGraphQLファイルをスキーマとして読み込みます。

プロジェクトを作成した際は次のスキーマがデフォルトで定義されています。

schema/schema.graphql
type Query {
  todos(first: Int): [Todo!]!
}

type Todo {
  title: String!
  content: String
  done: Boolean!
}

Resolverの実装

Queryのtodosを実装します。

src/graphql/query/todos.rs
pub async fn todos(ctx: &Context<'_>, first: Option<i32>) -> Vec<Todo> {
    let all_todos = vec![
        Todo {
            title: "Programming".to_string(),
            content: Some("Learn Rust".to_string()),
            done: false,
        },
        Todo {
            title: "Shopping".to_string(),
            content: None,
            done: true,
        },
    ];
    match first {
        Some(first) => all_todos.into_iter().take(first as usize).collect(),
        None => all_todos,
    }
}

コード生成

schema.graphqlを編集して、Queryにtodoを追加してみます。

schema.graphql
type Query {
  todos(first: Int): [Todo!]!
  # added
  todo(id: ID!): Todo
}

次のコマンドを実行することでGraphQLスキーマからRustのコードを生成できます。

rusty-gql generate // or rusty-gql g

あとは同様にsrc/graphql/query/todo.rsを実装するだけです。

Playground

rusty-gqlはGraphiQLのplaygroundをサポートしています。
ブラウザでhttp://localhost:3000/graphiqlにアクセスしてください。

ディレクトリ構造

rusty-gqlはRailsNext.jsと同じように設定より規約のポリシーで設計しています。
つまり、次のような規約に従ったディレクトリ構造でコードを生成します。

rusty-gql-project
 ┣ schema
 ┃ ┗ schema.graphql
 ┣ src
 ┃ ┣ graphql
 ┃ ┃ ┣ directive
 ┃ ┃ ┃ ┗ mod.rs
 ┃ ┃ ┣ input
 ┃ ┃ ┃ ┗ mod.rs
 ┃ ┃ ┣ mutation
 ┃ ┃ ┃ ┗ mod.rs
 ┃ ┃ ┣ query
 ┃ ┃ ┃ ┣ mod.rs
 ┃ ┃ ┣ resolver
 ┃ ┃ ┃ ┣ mod.rs
 ┃ ┃ ┣ scalar
 ┃ ┃ ┃ ┗ mod.rs
 ┃ ┃ ┗ mod.rs
 ┃ ┗ main.rs
 ┗ Cargo.toml

それぞれのディレクトリの役割は以下の通りです。

schema

.graphqlファイル
このディレクトリ配下であれば複数のGraphQLファイルをサポートしています。

src/graphql/query

Queryのresolver実装

src/graphql/mutation

Mutationのresolver実装

src/graphql/resolver

GraphQLのObject,Enum,Union,Interfaceのresolver実装

src/graphql/input

GraphQLのInputObjectの定義

src/graphql/scalar

Custom Scalarの定義

src/graphql/directive

Custom Directiveの定義

その他詳しい情報はドキュメントを参照してください。

今後について

まだまだ足りない機能がたくさんあるので、実装予定です。

  • Subscription
  • Dataloader
  • Calculate Query complexity
  • Apollo tracing
  • Apollo Federation
  • Automatic Persisted Query
    などなど...

ディレクトリの規約やコード生成もより良いものにしていきたいので、改善・変更する可能性があります。

また、内部実装の話ではあるのですが、GraphQLのparserなどはapollo-rsに変更するかもしれません。

Apolloが最近リリースしたApollo RouterもRust製ですし、今後GraphQL関連でRustのエコシステムがどのように発展していくのか楽しみです。

最後に

rusty-gqlの紹介でした。

まだまだラフな実装で改善点はたくさんあるのですが、今後も引き続き開発していきます。

また、starも押してもらえると開発の励みになるので、ぜひよろしくお願いします!

Discussion

rocchoroccho

Youtubeも拝見しました。すごくわくわくしています。ありがとうございます。

よかったら教えてください。Prismaはスキーマファーストに対する一般的な問題点を5つ挙げてくれていますが、( https://www.prisma.io/blog/the-problems-of-schema-first-graphql-development-x1mn4cb0tyl3

rusty-gqlで解決できている部分はありますか?
あるいは今後スキーマファーストのスタンスでも解決していけるだろうとお考えの点はあったりしますか?

可能なら、返信お待ちしております。