Kotlin DSL を利用して GraphQL のクエリを生成したいという気持ちがありました.

調べてみると, 特定のスキーマに対しての DSL を実装してみた, みたいなことをやっている方はいそうです. しかし, 実際に使われているような API についてガッチリ実装したものは見当たりません.

また, VerachadW/kraph のように, 汎用的に使えるようにした実装も存在します. とはいえやはり, Kotlin の DSL で書きたい, という気持ちを満たすにはちょっと違うなと感じますし, そもそも記述量が増えるだけで現実的なメリットもなさそうです.

GraphQL のクエリの見た目なるべくそのままで, Kotlin の世界でクエリを書けるようなものが欲しいと思いました.

アイデア

  • 特定のスキーマ定義だけではなく汎用的に使えるようにしたい
  • 安全で IDE での補完がきくようにしたい(文字列でフィールドを指定みたいなアプローチではなく)

なので, 一つのアプローチとして以下のような方針が考えられました.

GraphQL API スキーマ定義
         ↓
       (変換)
         ↓
API のための DSL 定義の Kotlin コード
         ↑
アプリケーションコードから呼び出す

実装した

前述の方針で実装してみたものが以下です.

lusingander/kraphql

例えば, 以下のようなスキーマ定義があるとき,

type Query {
    books: [Book!]!
    authors: [Author!]!
}

type Book {
    title: String!
    author: Author!
}

type Author {
    firstName: String!
    lastName: String!
    books: [Book!]!
}

生成された DSL 定義を利用すると以下のようなコードを書くことができます.

@Test
fun query() {
    val q = query {
        books {
            title
            author {
                firstName
                lastName
            }
        }
    }

    val expected = """
        query {
          books {
            title
            author {
              firstName
              lastName
            }
          }
        }
    """.trimIndent()

    val actual = formatQuery(q.toString()) // formatQuery just makes it look good

    assertThat(actual).isEqualTo(expected)
}

実例

実際に GitHub の GraphQL API のスキーマから生成した DSL がこちらです.

lusingander/kraphql-github

少なくとも現状ではコンパイルが通り利用可能なコードが生成されています.

というか, 有名かつ大規模で実際にユーザーに利用されている GraphQL API として, GitHub の API から生成できるところを最初のゴールにしていたのですが…

問題とか

結局のところ, レスポンスのための型も自動生成できないと嬉しくないわけで, これだけだと実用性はないだろうと思います.

クラス定義からクエリを生成する, みたいなアプローチになるのももっともだなと改めて理解しました.