この記事は GraphQL Advent Calendar 2020 9 日目の記事です。
前回の記事は @shinnoki さんの 2021年にGraphQL Code Generatorを使うなら、TypedDocumentNodeを使おう でした。
GraphQL クライアントライブラリは必須ではない
GraphQL は一般的には HTTP 上で JSON をやりとりする形で実装されます。
つまり、HTTP をしゃべることと JSON の parse さえできればそれで十分です。
言語によってはこれらはライブラリを使わずとも実現できるでしょう。
GraphQL クライアントライブラリは主に cache マネジメントのために用いられます。
つまり、cache をほとんど活用しないアプリケーションであれば GraphQL クライアントの必要性はほとんどありません。
TypeScript の GraphQL operation 用 codegen 事情
クライアントが必要ないとは言ったものの、レスポンスの型に対応する codegen が欲しくなりはします。
ここではもっともツールが揃っている TypeScript を例に示します。
GraphQL は型機能を提供しており、GraphQL schema と operation (query, mutation, subscription, fragment) から型が決定できます。
type Query {
me: User!
}
type User {
id: ID!
name: String!
}
という schema があり、
query {
me { name }
}
という operation があるとすると、そのレスポンスの型は TypeScript で書くと次のようになるでしょう。
interface Data {
me: { name: string }
}
そして次のように利用することができます。
const res = await fetch("/graphql", {method: "POST", body: JSON.stringify({query: "query { me { name } }"})});
const data: Data = (await res.json()).data;
クライアントが必要ないとは言うものの、operation (query { me { name } }
) から、この Data
という型を生成できると、operation に対して返ってくるべきレスポンスの型を間違えてないで済むため便利そうです。
TypeScript であれば、graphql-codegen の typescript-operations がこれに該当します。
Swift の GraphQL operation 用 codegen 事情
Swift 向けの codegen は、以下の2つがあるようです。
apollo-ios が生成するコードはあくまで apollo 前提で、apollo の機能を使わないのであればあまりうれしくありません。
graphql_swift_gen は schema に対して生成するようで、operation (query, mutation, subscription, fragment) に対して生成してくれるわけではないようです。
GraphQL はクライアントの operation の中身に対応してレスポンスが変化するため、これでは型の恩恵がイマイチ得られません。
先程の例でいくと、
type Query {
me: User!
}
type User {
id: ID!
name: String!
}
という定義に直接対応する型を生成しても、
interface Query {
me: User
}
interface User {
id: string
name: string
}
この User
のもつ field すべてが返ってくるとは限りません。
query {
me { name }
}
のような operation に対して生成しなければならないのです。
そこで、GraphQL Codegen の swift かつ operation 用 plugin として graphql-codegen-swift-operations を作ってみました。
graphql-codegen-swift-operations
具体的な使い方の例は https://github.com/mtsmfm/graphql-codegen-swift-operations/tree/master/swift-test-project にあります。
まず、GraphQL Codegen のチュートリアル通りにセットアップします。
次に、npm install --save-dev @mtsmfm/graphql-codegen-swift-operations
して、
codegen.yml で generated.swift を @mtsmfm/graphql-codegen-swift-operations
を使って生成するようにします。
schema: ./schema.graphql
documents: 'graphql/*.graphql'
generates:
Sources/app/generated.swift:
- '@mtsmfm/graphql-codegen-swift-operations'
あとは operation を書きます。
query AppQuery {
...
}
そして $(npm bin)/graphql-codegen
して graphql-codegen を実行すると、generated.swift が生成されます。
class AppQuery: Decodable {
let data: Data?;
let errors: Errors?;
public static let operationDefinition: String =
"""
query AppQuery {
...
}
"""
private enum CodingKeys: String, CodingKey {
case data
case errors
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.data = try container.decode(Data.self, forKey: .data)
self.errors = try container.decodeIfPresent(Errors.self, forKey: .errors)
}
struct Data: Decodable {
let organizations: [Internal_1_Organizations]
...
}
}
あとは AppQuery.operationDefinition
を query としてつかって POST しつつ、JSONDecoder
と AppQuery.self
を使って結果を取り出せばよいです。
let client = SynchronousHTTPClient()
do {
let decoder = JSONDecoder()
let data = try client.post(url: "http://localhost:4000/graphql", json: ["query": AppQuery.operationDefinition])
let result = try decoder.decode(AppQuery.self, from: data)
if let data = result.data {
for org in data.organizations {
print(org as Any)
}
}
} catch let error {
...
}
まとめ
GraphQL を叩くときにクライアントライブラリ選定から入ることが多いかもしれませんが、そもそも叩くだけなら必須ではないことを念頭に置くと違った視点が見えるかもしれません。
また、GraphQL Codegen の plugin を作るときは、
- schema に対してではなく schema + operation に対して吐く
- 特定のクライアントライブラリに依存しない
という点に留意して作るとよさそうです。