6135
6
0

GraphQL Coding Style Guide

Published at September 26, 2017 10:24 p.m.
Edited at March 12, 2021 5:31 p.m.

本文書は、 Kibela Web API の実装についてのスタイルガイドです。

実装の原則

Naming

フィールド名

※ graphql-ruby 1.8 以降は自動的にcamelizeされるようになっているので、任せましょう。

camelCase にしてください。

定義の際にmodel本来の名前と変える必要がありますが、クライアントサイド言語(JS, Swift, Java)はcamel caseが基本なので、そのまま使えるというメリットがあります。

なお camel case はいくつかのバリエーションがありますが ActiveSupport の String#camelize(:lower) に準じるようにしてください。

ruby
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) に準じるようにしてください。

ruby
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を返しても構いません。

参考:

Collection Type

コレクション型として、GraphQLの生の配列とRelay Connection型があります。

connectionは、edgesとnodeというラッパーオブジェクトのあるデータ型です。

たとえばKibelaのnotifiationsはこんな感じのリクエストをすると:

graphql
query {
  notifications(first: 5) {
    edges {
      node {
        id
        sender {
          account
          avatarSrcSet
        }
        messageHtml
        updatedAt
      }
    }
  }
}

こんな感じのresponseになります:

json
{
  "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 というフィールドを持っています。

例:

graphql
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)を埋め込める
      • しかしこれが必要になることはほぼない。はず。
  • nodesのメリット

基本的には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 をテストするためには必要ありません。