GraphQL API を利用する client のコードを書くとき、静的型付け言語においては codegen のアプローチが非常に有効です。なぜなら、すでに存在する GraphQL Schema から型を実質無料で手に入れることができるからです。
一方で、JavaScript や Ruby のような動的型付け言語が GraphQL API の client になるときはどうでしょうか? その場合でも codegen でクラスを生成してレスポンスをマッピングするアプローチは、一定の効用はあるかもしれません (クラスになってることでIDEのサポートとかが多少得られるかも、試してない) が、少なくとも静的型付け言語ほどの恩恵は望めません。
動的型付け言語の場合は、むしろその特性を生かすため、 codegen の煩雑さ (実質無料と書いておきながらですがw) を捨て、手軽さを重視したいところです。JavaScript であればふつうのobject, Ruby であれば OpenStruct を使うのが良いのではないでしょうか。あとは、その型であることをプログラマーが十分信じられればよいわけです。そこが静的型付け言語との考え方の違いです。
以下は、Ruby において GraphQL API を呼び出すコードです(雰囲気だけ)。
class FooRepository
FOO_QUERY = <<~EOS
query foo($id: ID!) {
foo(id: $id) {
bar {
baz
}
}
}
EOS
def self.find(id)
res = Faraday.post('.../graphql', { query: FOO_QUERY, variables: { id: id } })
# このへんでエラーハンドリングとかする
# OpenStructにマッピングする
# 正確には再帰的にやる必要がある
foo = OpenStruct.new(JSON.parse(res.body)['data'])
# 振る舞いをmixinすることもできる
foo.extend(SomeModule)
foo
end
end
以上のコードでは、メソッドのコードのすぐ上に GraphQL の query が配置されており、このメソッドからどんな型のオブジェクトが返ってくるかが分かりやすくなっています。動的型付け言語の場合は、この程度のゆるふわさで、プログラマーがどんな型で返ってくるかを信じられればそれで十分だと思います。
余談ですが、OpenStruct は存在しないキーにアクセスするとnilが返りますが、このときnilを返さずエラーをraiseする OpenStruct::Strict が提案されています ( https://github.com/ruby/ruby/pull/3594 ) 。GraphQL client としてはこちらの挙動のほうが使いやすいので、モンキーパッチ的に導入してもよいと思います。
昨今ではWebフロントエンド向けにはTypeScript全盛になり、SwiftやKotlinも合わせてクライアントサイドは静的型付け全盛の時代ですが、サーバーサイドでは Ruby (on Rails) などまだまだ動的型付け言語も健在です。そのため、 Server to Server での GraphQL API リクエストをする時は、動的型付け言語から行うこともあり得ます。その際は静的型付け言語でのプラクティスを無理に適用せず、動的型付け言語の良さを生かす形で実装するのがおすすめです。