Tech Racho エンジニアの「?」を「!」に。
  • 開発

RailsでGraphQL APIをつくる: Part 3 - ベストプラクティス集(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

RailsでGraphQL APIをつくる: Part 3 - ベストプラクティス集(翻訳)

このブログ記事シリーズでは以下の3つのトピックについて扱います。

  1. GraphQLとは何か
  2. Railsで基本的なAPIを書く
  3. ベストプラクティス集(本記事)

1. インターフェイス

2つの型が多数のフィールドを共有している状況を考えてみましょう。たとえば、2つの型のどちらにもidupdated_atcreated_atといったActiveRecordの基本的なフィールドがあるとします。こうした型を整然と扱うにはどうしたらよいでしょうか。1つの方法は、次の「インターフェイス」を使うことです。

リファクタリング前のUserTypePostTypeは次のようになっています。

# app/graph/types/user_type.rb
UserType = GraphQL::ObjectType.define do
  name "User"
  description "1人のユーザー"

  field :id, types.Int
  field :email, types.String
  field :updated_at do
    type types.Int

    resolve -> (obj, args, ctx) {
      obj.updated_at.to_i
    }
  end
  field :created_at do
    type types.Int

    resolve -> (obj, args, ctx) {
      obj.created_at.to_i
    }
  end
end
# app/graph/types/post_type.rb
PostType = GraphQL::ObjectType.define do
  name "Post"
  description "1件の投稿"

  field :id, types.Int
  field :title, types.String
  field :content, types.String
  field :updated_at do
    type types.Int

    resolve -> (obj, args, ctx) {
      obj.updated_at.to_i
    }
  end
  field :created_at do
    type types.Int

    resolve -> (obj, args, ctx) {
      obj.created_at.to_i
    }
  end
end

そこで、active_record_interfaces.rbファイルを新たに作成して、idupdated_atcreated_atフィールドを定義します。

# app/graph/types/active_record_interfaces.rb

ActiveRecordInterface = GraphQL::InterfaceType.define do
  name "ActiveRecord"
  description "Active Recordインターフェイス"

  field :id, types.Int
  field :updated_at do
    type types.Int

    resolve -> (obj, args, ctx) {
      obj.updated_at.to_i
    }
  end
  field :created_at do
    type types.Int

    resolve -> (obj, args, ctx) {
      obj.created_at.to_i
    }
  end
end

これで、UserTypePostTypeを次のようにすっきりと書き直せます。

# app/graph/types/user_type.rb

UserType = GraphQL::ObjectType.define do
  interfaces [ActiveRecordInterface]
  name "User"
  description "1人のユーザー"

  field :email, types.String
end
# app/graph/types/post_type.rb

PostType = GraphQL::ObjectType.define do
  interfaces [ActiveRecordInterface]
  name "Post"
  description "1件の投稿"

  field :title, types.String
  field :content, types.String
end

2. Serviceオブジェクト

resolveブロックは各フィールドの振る舞いを扱いますが、resolveブロックにコードロジックを直接追加するとresolveブロックが不安定になり、テストも行いにくくなってしまいます。よりよい方法は、ロジックをresolveブロックから切り離してServiceオブジェクトに閉じ込めることです。なお、Serviceオブジェクトは抽象化のためのものなので、好みのデザインパターンがあればそれを使って抽象化してもかまいません。

以下のコード例をご覧ください。

CreatePostMutation = GraphQL::Relay::Mutation.define do
  # ...

  resolve -> (object, inputs, ctx) {
    post = ctx[:current_user].posts.create(title: inputs[:title], content: inputs[:content])

    {
      post: post
    }
  }
end

上のコードは以下のように書き換えられます。

CreatePostMutation = GraphQL::Relay::Mutation.define do
  # ...

  resolve -> (object, inputs, ctx) {
    Graph::CreatePostService.new(inputs, ctx).perform!
  }
end

こうしておけばGraph::CreatePostServiceだけをテストすれば済むので、テストやメンテナンスが楽になります。

3. Relayモジュールを使う

graphql-ruby gemRelay名前空間にはいくつかの便利なモジュールがあります。APIをGitHub GraphQL APIのようにしたい場合や、Relayに統合するだけなら、これらのモジュールを使うことで多くの時間を節約できます。

以下のモジュールの使い心地を試してみるとよいでしょう。

  • GraphQL::Relay::ConnectionType
  • GraphQL::Relay::Node
  • GraphQL::Relay::Edge

以上、私が学んできた経験をご紹介いたしました。この他にもGraphQLとRailsで何かよい手法を見つけたら、ぜひgistのコメントでお知らせください。
お読みいただきありがとうございました。

関連記事

RailsでGraphQL APIをつくる: Part 1 – GraphQLとは何か(翻訳)

RailsでGraphQL APIをつくる: Part 2 – Railsで基本的なAPIを書く(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。