GraphQL Coding Style Guide
本文書は、 Kibela Web API の実装についてのスタイルガイドです。
実装の原則
- GraphQL official site に従う
- GraphQLに足りない部分は Relay server specに従う
- 実例としてはGraphQL+Relayで構築されているGitHub API v4を参考にする
Naming
フィールド名
※ graphql-ruby 1.8 以降は自動的にcamelizeされるようになっているので、任せましょう。
camelCase
にしてください。
定義の際にmodel本来の名前と変える必要がありますが、クライアントサイド言語(JS, Swift, Java)はcamel caseが基本なので、そのまま使えるというメリットがあります。
なお camel case はいくつかのバリエーションがありますが ActiveSupport の String#camelize(:lower)
に準じるようにしてください。
puts "content_html".camelize(:lower) # => "contentHtml"
HTMLをもつフィールド
フィールドの値がHTMLの場合は、 Html
というサフィックスを付けてください。
例: blogs.content
(markdown) vs blogs.contentHtml
(HTML)
※ GitHub API v4では HTML
というサフィックスですが、 String#camelize
の結果を使いたい(=ルールに人の判断をいれたくない)のでこのようにします。このネーミングルールは、フィールド名だけでなく、JavaScriptの変数名などでも同様のネーミングを意識しましょう。
タイプ名
タイプ名は UpperCamelCase
にしてください。
なお camel case はいくつかのバリエーションがありますが ActiveSupport の String#camelize(:upper)
に準じるようにしてください。
puts "xml_http_request".camelize(:upper) # => "XmlHttpRequest"
Enum
Enumの要素の名前と値は大文字の SNAKE_CASE
にしてください。
ID
データベースのレコードIDそのままではなく、global unique id (Relay ID) を生成してそれを使います。これにより、refetchingが容易になります。
Kibelaの場合は、 RelayUtility
/ RelayIdentifiable
でRelay IDを生成するための仕組みを提供しています。
なお、このようなIDであるため、クライアントサイドでURL / pathを構築できません。GraphQL APIで必要なURL / pathを取得できるようにすべきです。
ただし、データベースのレコードIDがクライアント側で必要な場合には、databaseId
フィールドとしてIDを返しても構いません。
参考:
- https://facebook.github.io/relay/docs/graphql-object-identification.html
- http://graphql-ruby.org/relay/object_identification.html
Collection Type
コレクション型として、GraphQLの生の配列とRelay Connection型があります。
connectionは、edgesとnodeというラッパーオブジェクトのあるデータ型です。
たとえばKibelaのnotifiationsはこんな感じのリクエストをすると:
query {
notifications(first: 5) {
edges {
node {
id
sender {
account
avatarSrcSet
}
messageHtml
updatedAt
}
}
}
}
こんな感じのresponseになります:
{
"data": {
"notifications": {
"edges": [
{
"node": {
"id": "Tm90aWZpY2F0aW9uLzE4MQ",
"sender": {
"account": "member_user",
"avatarSrcSet": "{\"1x\":\"/assets/avatar/avatar3-xxx.svg\",\"2x\":\"/assets/avatar/avatar3-xxx.svg\"}"
},
"messageHtml": "<strong>member_user</strong> has <strong>commented</strong> on <span class=\"notification-entryTitle\">foo bar</span>.",
"updatedAt": "2017-09-07T15:54:04+09:00"
}
}
]
}
}
}
edgesとnodeというラッパーがあると冗長にみえますが、それぞれのレイヤにカスタムデータを差し込めるようにするためです。たとえば、notifications(や、ほかの多くのKibelaのデータ型)は素のconnection型を拡張して edges.totalCount
というフィールドを持っています。
例:
query {
notifications(state: UNREAD) {
totalCount
}
}
notifications直下に直接配列を置いている場合、totalCountは提供できません。
Connection vs. Array
- コレクション型のフィールドは基本的にconnection型にします
- いかなるケースでも要素をすべて取得する フィールドはarray型にしてもかまいません
edges.node
vs. nodes
Relay Connectionを取得するには、edges.node
フィールドにアクセスするか、nodes
フィールドにアクセスするか、2つの方法があります。
-
edges.node
のメリット- 各Nodeに対応するメタデータ(
cursor
)を埋め込める- しかしこれが必要になることはほぼない。はず。
- 各Nodeに対応するメタデータ(
-
nodes
のメリット- 各Nodeに対応するメタデータは埋め込めない分、クライアント側の実装が単純にできる
- 件数が多い場合、有意に
nodes
のほうが速い https://github.com/bitjourney/kibela/pull/11446
基本的にはnodes
を使用します。cursor
が必要になるケース(ほとんどないと思いますが)では、edges
でないと実装できないため、そちらを使いましょう。
Mutations
update系mutationは特に理由がない限り冪等にする
RESTful APIのPUTないしPATCHに相当するupdate系のmutationは、RESTful APIのときと同じく、冪等にしてください。つまり、本来やりたかった変更がすでに行われていた場合、単に何も操作せず、エラーも返さないようにすべきです。
ただし、ユーザーにメッセージを与えるために、ヒントとなる値を返すことはあるでしょう。
冪等にするのは、成功したリクエストのレスポンスをネットワークエラーなどで受け取れなかった場合に安全にリトライするためです。
エラー
GraphQLリクエスト自体が成功した場合はHTTP status codeは200にすべきです。Rubyレベルの例外をすべてrescueして GraphQL::ExecutionError
に変換するとこの振る舞いにできます。
それぞれの query / mutation でのエラーは errors.extensions.code
でエラーコードを通知してください。クライアントはエラーコードと errors.extensions
に含まれる情報を使ってクライアントサイドでユーザー向けのメッセージを構築してください。これは、クライアントサイドのコンテキストによって適切なメッセージが異なるためです。
errors.message
にはAPI利用者(開発者)向けのメッセージを入れてください。
なお、Kibelaで使うエラーは GraphqlError
module に定義しています。
参考:
Testing
spec/graphql/
に、 KibelaSchema.execute
で個別の query / mutation をテストするspecを書いてください。
request specは1件だけ疎通確認のために存在しますが、個別のquery / mutation をテストするためには必要ありません。