はじめに
メリークリスマス!
クリスマスはイエス・キリストの誕生日だと思っていたんですが、あくまで誕生を祝う日であって、イエス・キリストの誕生日ではないということを最近知りました。
クリスマス(英: Christmas)は「キリストのミサ」という意味で、一部の教派が行うイエス・キリストの降誕祭[1]。あくまで誕生を祝う日であって、イエス・キリストの誕生日ではない[2]。
毎年12月25日に祝われるが、正教会のうちユリウス暦を使用するものは、グレゴリオ暦の1月7日に該当する日にクリスマスを祝う[3][4]。ただし、キリスト教で>最も重要な祭と位置づけられるのはクリスマスではなく、復活祭である[5][6][7][8]。
キリスト教に先立つユダヤ教の暦、ローマ帝国の暦、およびこれらを引き継いだ教会暦では, 現代の常用時とは異なり、日没を一日の境目としているので、クリスマス・イヴと呼ばれる12月24日夕刻から12月25日朝までも、教会暦上はクリスマスと同じ日に数えられる[9]。したがって、教会暦ではクリスマスは「12月24日の日没から12月25日の日没まで」である。
https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AA%E3%82%B9%E3%83%9E%E3%82%B9
せっかくクリスマスの日に担当になったので、クリスマスに絡めたネタを書こうと思っていたのですが、全く思いつかないので普通に記事を書きます。
これはHameeアドベントカレンダー25日目の記事です。
背景
普段、ふるさと納税関連のお仕事をしています。現在、アプリケーションエンジニアチームは2人。複数のアプリケーションを開発していて、 API開発での課題感がでてきました。
- エンドポイントがたくさん。IFを実装するたびに都度作成していて、管理しにくくなってきている
- スピード重視・少人数のため、APIドキュメント管理できていない
上記のこと、またいまさらですがGraphQLをやってみたかったので、やってみることにしました。
GraphQLとは
GraphQLの紹介は多くの方が記事にされているので、簡単にまとめます。(RESTとの違いについてもここでは説明しません)
GraphQLはFacebookが開発しているWeb APIのための規格で、「クエリ言語」と「スキーマ言語」からなります。
以下の特徴があります。
- スキーマ(Web APIの仕様)を構築し、クエリでどのようなデータを取得するかを表現
- 単一のエンドポイント(/graphql)のみ。ほしいデータはhttp bodyに明記して取得可能
- GraphiQLという専用のIDEを使えば、スキーマ等のドキュメントも確認可能
- クエリ言語にはデータ取得系のquery、データ更新系のmutation、pub/subモデルでサーバーサイドのイベントを受け取るsubscriptionがある
以下の記事は大変参考にさせて頂きました。
「GraphQL」徹底入門 ─ RESTとの比較、API・フロント双方の実装から学ぶ
Web API初心者と学ぶGraphQL
Hello worldやってみる
今回はRubyライブラリとして提供されているgraphql、graphql-railsを使って、GraphQLをやってみます。
実行環境
version | |
---|---|
ruby | 2.6.3 |
rails | 5.2.3 |
gem graphql | 1.11.6 |
gem graphql-rails | 1.7.0 |
手順
gem 'graphql'
group :development, :test do
gem 'graphiql-rails'
end
Gemfileに記載後、以下のコマンドを実行します。
実行後に以下のファイル郡が作成されます。
$ bundle install
$ rails generate graphql:install
$ bundle exec rails g graphql:install
create app/graphql/types
create app/graphql/types/.keep
create app/graphql/app_schema.rb
create app/graphql/types/base_object.rb
create app/graphql/types/base_argument.rb
create app/graphql/types/base_field.rb
create app/graphql/types/base_enum.rb
create app/graphql/types/base_input_object.rb
create app/graphql/types/base_interface.rb
create app/graphql/types/base_scalar.rb
create app/graphql/types/base_union.rb
create app/graphql/types/query_type.rb
add_root_type query
create app/graphql/mutations
create app/graphql/mutations/.keep
create app/graphql/mutations/base_mutation.rb
create app/graphql/types/mutation_type.rb
add_root_type mutation
create app/controllers/graphql_controller.rb
route post "/graphql", to: "graphql#execute"
gemfile graphiql-rails
route graphiql-rails
Gemfile has been modified, make sure you `bundle install`
また、専用のIDEも/graphiqlで確認できます。
(私の場合は初期画面ではQUERY VALIABLESのみしか見えない状態でしたが、リロードで表示できました)
最初に以下のクエリを実行(左側)して、実行結果が正しく表示されていれば(右側)、Hello world成功です。
query {
testField
}
また、右上のDocs>queryを選択すると、API側で設定されているスキーマ定義やクエリを確認することができます。
サンプルデータでやってみる
年末なので、ふるさと納税に絡めて、リレーションがある3つのサンプルデータを作ってみます。
- local_governments: 自治体
- id: 自治体ID
- suppliers: 返礼品事業者
- id: 返礼品事業者ID
- local_government_id: 自治体ID
- return_gifts: 返礼品
- id: 返礼品ID
- supplier_id: 返礼品事業者ID
- name: 返礼品名
スキーマ定義
まずはスキーマ定義からです。
各テーブルと対になる形で定義していきます。
module Types
class LocalGovernmentType < Types::BaseObject
field :id, ID, null: false
field :suppliers, [Types::SupplierType], null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
end
end
module Types
class SupplierType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
def name
object.info['supplier_name']
end
field :return_gifts, [Types::ReturnGiftType], null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
end
end
↑objectという変数でResolverからオブジェクトが渡されます。suppliers.nameは本来存在しないのですが、json型で定義されているデータを渡してます。decoratorみたいな感じで使えるのかなと思います。
module Types
class ReturnGiftType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
field :supplier, Types::SupplierType, null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
end
end
Query
サンプルデータから特定の自治体に紐づく返礼品事業者、返礼品すべてを取得したいと思います。
定義
Query定義、Resolver作成をしていきます。
module Types
class QueryType < Types::BaseObject
field :local_government, LocalGovernmentType, null: false, description: '自治体情報' do
argument :id, Int, '自治体ID', required: true
end
def local_government(id:)
LocalGovernment.find_by(id: id)
end
end
end
自治体情報はIDで条件指定をできるようにしておきます。
実行
query {
localGovernment(id: 1){
id
suppliers{
id
returnGifts{
id
name
}
}
}
}
上記のクエリを実行すると、下記のレスポンスが返却されることがわかります。
{
"data": {
"localGovernment": {
"id": "1",
"suppliers": [
{
"id": "1",
"name": "返礼品事業者X",
"returnGifts": [
{
"id": "1",
"name": "返礼品A"
},
{
"id": "4",
"name": "返礼品B"
},
{
"id": "5",
"name": "返礼品C"
}
]
},
{
"id": "5",
"name": "返礼品事業者Y",
"returnGifts": [
{
"id": "11",
"name": "返礼品I"
},
{
"id": "12",
"name": "返礼品J"
}
]
}
]
}
}
}
関連情報も上手く取得できています。
Mutation
次は特定の返礼品事業者の返礼品情報を追加したいと思います。
流れとしては、Mutationクラス作成→Mutation定義作成→クエリ実行になります。
Mutationクラス
module Mutations
class CreateReturnGiftMutation < Mutations::BaseMutation
argument :supplier_id, Int, required: true
argument :name, String, required: true
field :return_gift, Types::ReturnGiftType, null: true
field :errors, [String], null: false
def resolve(supplier_id:, name:)
return_gift = ReturnGift.new(
supplier_id: supplier_id,
name: name
)
if return_gift.save
{
return_gift: return_gift,
errors: []
}
else
{
return_gift: nil,
errors: return_gift.errors.full_messages
}
end
end
end
end
Mutation定義
module Types
class MutationType < Types::BaseObject
field :create_return_gift, mutation: Mutations::CreateReturnGiftMutation
end
end
実行
mutation {
createReturnGift(input: {
supplierId: 5
name: "追加した返礼品"
})
{
returnGift{
id
supplier{
id
name
returnGifts{
id
name
}
}
}
}
}
上記のクエリを実行すると、下記のレスポンスが返却されることがわかります。
返礼品データも作成されています。
{
"data": {
"createReturnGift": {
"returnGift": {
"id": "24",
"supplier": {
"id": "5",
"name": "返礼品事業者Y",
"returnGifts": [
{
"id": "11",
"name": "返礼品I"
},
{
"id": "12",
"name": "返礼品J"
},
{
"id": "24",
"name": "追加した返礼品"
}
]
}
}
}
}
}
おわりに
今回はGraphQLでデータ取得、データ作成をやってみました。
意識せずにドキュメントが作られる点(Graphiql便利すぎ)や単一エンドポイントな点、(正しく命名できていれば)クエリが直感的でわかりやすい点など、結構感動しました。
他にもgraphql-batchを使った場合や型定義など、ベストプラクティスを学んでいきたいと思います。
Hameeアドベントカレンダー2020お疲れさまでした!来年もまたやりましょおおお
(あと、来年度控除のふるさと納税は12/31になってます。まだ間に合うのでやってない方はぜひ。)
https://yummy.hamee-furusato.jp/
REF
graphql-ruby.org
【Rails】graphql-rubyでAPIを作成
GraphQL Ruby の使い方 (基礎編)
Ruby on RailsエンジニアがGraphQLを使うと簡単に最強のWebAPIを作れる話。(デモもあるよ!)