Ruby:Shopify GraphQL Admin API の使い方
Ruby で Shopify GraphQL Admin API を使う方法についてまとめます。
GraphQL API を使うべき理由
公式ドキュメントの Shopify Admin API のページに
The Admin API is available through GraphQL and REST.
と記載されている通り、
Shopify Admin API 自体は、GraphQL API・REST API のどちらでも利用できます。
しかし Shopify としては、GraphQL APIの利用が推奨されているので、今後のアップデートを考えると、GraphQL API の方を使っておくのがベターです。
gem shopify_api でもそのことが明記されています:
The best way to consume the Shopify API is through GraphQL, which enables high volume mutations, bulk operations, and access to all new features.
なので、今後は REST API でなく、GraphQL API を使っていきましょう。
REST API との違い
GraphQL と REST の違いをまとめると次の表の通りです。
項目 | GraphQL | REST |
---|---|---|
利用方法 | HTTP | HTTP |
エンドポイント | 1つ | 複数 |
1リクエストで取得可能なリソース | 複数 | 1つ |
必要以上のデータ取得 | 起こらない | 起こる |
GraphQLはエンドポイントが単一で、Client 側のニーズに応じてデータを取得できるので、API リクエストの効率が良いです。
用語の整理
GraphQL で出てくる用語をざっくり整理します。これが分かっていると、後続の説明がすんなり入ってきます。
参考: In GraphQL what's the meaning of “edges” and “node”?
用語 | 説明 |
---|---|
connection | オブジェクトデータとメタデータがまとまったもの(edges や pageInfo などで構成される) |
field | オブジェクトが持っているデータ構造 |
pageInfo | hasNextPage, hasPreviousPage, startCursor, endCursor などのページ情報 |
edge | オブジェクトの配列データ(node、cursor で構成される) |
node | オブジェクトデータ(product、productVariant などのデータ) |
cursor | edge のポジションを表す Base64 エンコード文字列(ページネーションで利用される) |
Query Root
すべてのGraphQLのクエリは、QueryRoot に記載の Connections
もしくは Fields
のいずれかの項目をトップレベルとして書き始めます。
この表、パッと見、分かりにくいですが(僕だけかもしれませんが)、各項目ごとに tr
の色がストライプ装飾されています。それが分かるとそこそこ見やすいです。
サンプルクエリ
各項目の詳細ページ末尾にいくと、「GraphiQL(ツール)」でサンプルリクエストを呼び出せます。これで実際にどんな値が返ってくるか確認できます。
リクエスト制限
コスト
GraphQL API ではリクエストするクエリの複雑さにより「コスト」が計算されます。そのコストが制限を超える場合は、エラーが返されます。
コスト計算の詳細:こちら
制限
Shopify API のリクエスト制限には a leaky bucket algorithm が使われています。
そして、GraphQL API のバケツの大きさは「1000pt」、リークレートが「50pt/sec」です。
An app is given a bucket of 1,000 cost points, with a leak rate of 50 cost points per second.
制限の詳細:こちら
例えば、products 50件
と、それに紐づく productVariants 最大100件
を同時に取得しようとすると、クエリコストが 5152 > 1000
となり、一発で制限超過のエラーが出てしまいます!
Ruby での導入方法
それでは本題に入り、Ruby で Shopify GraphQL Admin API を導入する方法を整理します。
shopify_api
gem REST API と同じ gem shopify_api で利用できます。
gem 'shopify_api'
- gem のバージョンは
9.0.2
- Ruby のバージョンは
2.4.10
- Shopify API のバージョンは
2020-10
認証方法
APIの認証方法も、REST API と同じです。session
をアクティベートすれば、API を利用できます。
session = ShopifyAPI::Session.new(
domain: domain,
token: token,
api_version: API_VERSION
)
ShopifyAPI::Base.activate_session(session)
# APIコール
ShopifyAPI::Base.clear_session
設定エラー InvalidClient が出る場合
ShopifyAPI::GraphQL のスキーマファイルが生成されていないと、API呼び出し時に次のエラーが出ます。
ShopifyAPI::GraphQL::InvalidClient
以下のコマンドを叩くことでスキーマファイルが生成され、エラーが解消します。このスキーマファイルは「開発環境でのクエリの有効性チェックのため」に必要らしく、任意のストア情報で、1度生成すればOKです。
rake shopify_api:graphql:dump SHOP_DOMAIN="SHOP_NAME.myshopify.com" ACCESS_TOKEN="SHOP_TOKEN" API_VERSION=yyyy-mm
参考:How to use GraphQL schema for public apps?
It appears the dump is to "ensure your queries are valid in development", so you'll only need to generate it once - perhaps with test store credentials?
Ruby での使い方
gem shopify_api
での GraphQL API の呼び出し方法を整理します。
基本
基本的な呼び出し方は次の通りです。
Call
例:Shop 名を取得
client = ShopifyAPI::GraphQL.client
SHOP_NAME_QUERY = client.parse <<-'GRAPHQL'
{
shop {
name
}
}
GRAPHQL
result = client.query(SHOP_NAME_QUERY)
ポイント
-
client.parse
で文字列クエリをGraphQLクエリにパース
Result
pry(main)> result
=> #<GraphQL::Client::Response:0x00007fa9e0ccfb00
@data=#< shop=...>,
@errors=#<GraphQL::Client::Errors @messages={} @details={}>,
@extensions={"cost"=>{"requestedQueryCost"=>1, "actualQueryCost"=>1, "throttleStatus"=>{"maximumAvailable"=>1000.0, "currentlyAvailable"=>999, "restoreRate"=>50.0}}},
@original_hash=
{"data"=>{"shop"=>{"name"=>"MY SHOP NAME"}},
"extensions"=>{"cost"=>{"requestedQueryCost"=>1, "actualQueryCost"=>1, "throttleStatus"=>{"maximumAvailable"=>1000.0, "currentlyAvailable"=>999, "restoreRate"=>50.0}}}}>
pry(main)> result.data.shop
=> #< name="MY SHOP NAME">
変数あり
GRAPHQL クエリの値を動的に定義したい場合は、変数 variables
を持たせます。
Call
例:Products を50件取得
client = ShopifyAPI::GraphQL.client
PRODUCTS_QUERY = client.parse <<-'GRAPHQL'
query($per_page: Int){
products(first: $per_page) {
edges {
cursor
node {
id
}
}
}
}
GRAPHQL
variables = {
per_page: 50
}
result = client.query(PRODUCTS_QUERY, variables: variables)
ポイント
- 変数を
query($[name]: [type]){}
で定義 - 定義した変数は、文字列クエリ内で
$[name]
で参照できる -
client.query
の第二引数variables
に動的な値を渡す
Result
pry(main)> result
=> #<GraphQL::Client::Response:0x00007fa9df661350
@data=#< products=...>,
@errors=#<GraphQL::Client::Errors @messages={} @details={}>,
@extensions={"cost"=>{"requestedQueryCost"=>52, "actualQueryCost"=>52, "throttleStatus"=>{"maximumAvailable"=>1000.0, "currentlyAvailable"=>948, "restoreRate"=>50.0}}},
@original_hash=
{"data"=>
{"products"=>
{"edges"=>
[{"cursor"=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0", "node"=>{"id"=>"gid://shopify/Product/1000000000000"}},
{"cursor"=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1", "node"=>{"id"=>"gid://shopify/Product/1000000000001"}},
...
pry(main)> result.data.products.edges.first.node
=> #< id="gid://shopify/Product/1000000000000">
ページネーション
Shopify GraphQL API のベージネーションは、cursor-based pagination
です。
Paginating results with GraphQL
When you use a connection to retrieve a list of resources, you use arguments to specify the number of results to retrieve. You can select which set of results to retrieve from a connection by using cursor-based pagination.
connections (products など)のクエリパラメータに after: [cursor]
を渡すことで、指定した cursor 以降の edges データを取得することができます。
次ページの有無は、connections.pageInfo.hasNextPage
で把握できます。
Call
例:指定した任意の cursor 以降の Products を2件取得
client = ShopifyAPI::GraphQL.client
PRODUCTS_QUERY = client.parse <<-'GRAPHQL'
query($per_page: Int, $cursor: String){
products(first: $per_page, after: $cursor) {
pageInfo {
hasNextPage
}
edges {
cursor
node {
id
}
}
}
}
GRAPHQL
variables = {
per_page: 2,
cursor: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1'
}
result = client.query(PRODUCTS_QUERY, variables: variables)
Result
pry(main)> result
=> #<GraphQL::Client::Response:0x00007fa9ddfe9b68
@data=#< products=...>,
@errors=#<GraphQL::Client::Errors @messages={} @details={}>,
@extensions={"cost"=>{"requestedQueryCost"=>4, "actualQueryCost"=>4, "throttleStatus"=>{"maximumAvailable"=>1000.0, "currentlyAvailable"=>996, "restoreRate"=>50.0}}},
@original_hash=
{"data"=>
{"products"=>
{"pageInfo"=>{"hasNextPage"=>true},
"edges"=>
[{"cursor"=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2", "node"=>{"id"=>"gid://shopify/Product/1000000000002"}},
{"cursor"=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3", "node"=>{"id"=>"gid://shopify/Product/1000000000003"}}]}},
"extensions"=>{"cost"=>{"requestedQueryCost"=>4, "actualQueryCost"=>4, "throttleStatus"=>{"maximumAvailable"=>1000.0, "currentlyAvailable"=>996, "restoreRate"=>50.0}}}}>
pry(main)> result.data.products.page_info.has_next_page?
=> true
connections
での検索(query
)
connections の結果を絞りたい時は、クエリパラメータに query: String
を渡します。
query のシンタックス: こちら
Call
例:指定した product_id に該当する Products を1件取得
client = ShopifyAPI::GraphQL.client
PRODUCTS_QUERY = client.parse <<-'GRAPHQL'
query($query: String){
products(first: 1, query: $query) {
edges {
cursor
node {
id
}
}
}
}
GRAPHQL
variables = {
query: 'id:1000000000000'
}
result = client.query(PRODUCTS_QUERY, variables: variables)
ポイント
- レスポンスの node.id は
gid
(gid://shopify/[object]/[id
) というフォーマットだが、検索クエリで id を指定する場合は[id]
の部分のみを用いる(ID
型が要求される時はgid
を使う)
Result
pry(main)> result
=> #<GraphQL::Client::Response:0x00007fa9e25f3ca8
@data=#< products=...>,
@errors=#<GraphQL::Client::Errors @messages={} @details={}>,
@extensions={"cost"=>{"requestedQueryCost"=>3, "actualQueryCost"=>3, "throttleStatus"=>{"maximumAvailable"=>1000.0, "currentlyAvailable"=>997, "restoreRate"=>50.0}}},
@original_hash=
{"data"=>{"products"=>{"edges"=>[{"cursor"=>"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0", "node"=>{"id"=>"gid://shopify/Product/1000000000000"}}]}},
"extensions"=>{"cost"=>{"requestedQueryCost"=>3, "actualQueryCost"=>3, "throttleStatus"=>{"maximumAvailable"=>1000.0, "currentlyAvailable"=>997, "restoreRate"=>50.0}}}}>
pry(main)> result.data.products.edges.first.node
=> #< id="gid://shopify/Product/1000000000000">
nodes
での検索(複数ID)
複数IDなど、複数値で検索する時は、nodes
オブジェクトを使います。
フォーラムでの関連回答: こちら
Another option is the nodes field on QueryRoot, which takes an array of ids.
Call
例:指定した複数の ids に該当する Products を取得
client = ShopifyAPI::GraphQL.client
PRODUCTS_QUERY = client.parse <<-'GRAPHQL'
query ($ids: [ID!]!){
nodes(ids: $ids) {
... on Product {
id
}
}
}
GRAPHQL
variables = {
ids: ["gid://shopify/Product/1000000000000", "gid://shopify/Product/1000000000001"]
}
result = client.query(PRODUCTS_QUERY, variables: variables)
ポイント
-
nodes
の ID にはgid
(gid://shopify/[object]/[id
) のフォーマットで指定する -
... on [Object]
で取得したいオブジェクトを指定する - 返ってくる値に
edges
は含まれない
Result
pry(main)> result
=> #<GraphQL::Client::Response:0x00007f9560413690
@data=#< nodes=...>,
@errors=#<GraphQL::Client::Errors @messages={} @details={}>,
@extensions=
{"cost"=>
{"requestedQueryCost"=>3,
"actualQueryCost"=>3,
"throttleStatus"=>
{"maximumAvailable"=>1000.0, "currentlyAvailable"=>997, "restoreRate"=>50.0}}},
@original_hash=
{"data"=>
{"nodes"=>
[{"__typename"=>"Product",
"id"=>"gid://shopify/Product/1000000000000"},
{"__typename"=>"Product",
"id"=>"gid://shopify/Product/1000000000001"}]
pry(main)> result.data.nodes
=> [#< __typename="Product" id="gid://shopify/Product/1000000000000" >,
#< __typename="Product" id="gid://shopify/Product/1000000000001" >]
まとめ
- 今後のアップデートを考えると、REST API ではなく GraphQL API の方を使っておくのがベター。
- GraphQLのクエリは QueryRoot に記載の
Connections
もしくはFields
のいずれかの項目からスタートさせる。 - リクエスト制限に引っかかる場合は、クエリフィールドやオブジェクトのネストを絞る。
- Ruby では REST API と同じ gem shopify_api で利用できる。
- ベージネーションの仕様は
cursor-based pagination
。 - 複数IDの検索は
connections(query; String)
ではなくnodes(ids: [ID])
を使う。
Discussion