From ab800757ce74fb5397627ebe88998160c87412d6 Mon Sep 17 00:00:00 2001 From: Scott Walkinshaw Date: Tue, 21 Jan 2020 17:04:09 -0500 Subject: [PATCH] Add documentation --- README.md | 15 ++- docs/graphql.md | 191 ++++++++++++++++++++++++++++++ lib/shopify_api/graphql/task.rake | 56 +++++++-- 3 files changed, 245 insertions(+), 17 deletions(-) create mode 100644 docs/graphql.md diff --git a/README.md b/README.md index d16e7c909..6608ac471 100644 --- a/README.md +++ b/README.md @@ -347,15 +347,18 @@ gem install shopify_api_console ## GraphQL -This library also supports Shopify's new [GraphQL API](https://help.shopify.com/api/graphql-admin-api) -via a dependency on the [graphql-client](https://github.com/github/graphql-client) gem. +Note: the GraphQL client has improved and changed in version 9.0. See the [client documentation](docs/graphql.md) +for full usage details and a [migration guide](docs/graphql.md#migration-guide). + +This library also supports Shopify's [GraphQL Admin API](https://help.shopify.com/api/graphql-admin-api) +via integration with the [graphql-client](https://github.com/github/graphql-client) gem. The authentication process (steps 1-5 under [Getting Started](#getting-started)) -is identical. Once your session is activated, simply construct a new graphql -client and use `parse` and `query` as defined by +is identical. Once your session is activated, simply access the GraphQL client +and use `parse` and `query` as defined by [graphql-client](https://github.com/github/graphql-client#defining-queries). ```ruby -client = ShopifyAPI::GraphQL.new +client = ShopifyAPI::GraphQL.client SHOP_NAME_QUERY = client.parse <<-'GRAPHQL' { @@ -369,6 +372,8 @@ result = client.query(SHOP_NAME_QUERY) result.data.shop.name ``` +[GraphQL client documentation](docs/graphql.md) + ## Threadsafety ActiveResource is threadsafe as of version 4.1 (which works with Rails 4.x and above). diff --git a/docs/graphql.md b/docs/graphql.md new file mode 100644 index 000000000..1e250daf7 --- /dev/null +++ b/docs/graphql.md @@ -0,0 +1,191 @@ +# GraphQL client + +The `shopify_api` gem includes a full featured GraphQL client to interact with +Shopify's [GraphQL Admin API](https://help.shopify.com/en/api/graphql-admin-api). +GitHub's [graphql-client](https://github.com/github/graphql-client) is used as +the underlying client and this library integrates it with our existing +session, authentication, and API versioning features. + +## Example + +```ruby +client = ShopifyAPI::GraphQL.client + +SHOP_NAME_QUERY = client.parse <<-'GRAPHQL' + { + shop { + name + } + } +GRAPHQL + +result = client.query(SHOP_NAME_QUERY) +result.data.shop.name +``` + +* [Getting started](#getting-started) +* [Rails integration](#rails-integration) +* [API versioning](#api-versioning) +* [Initialization process](#initialization-process) +* [Migration guide](#migration-guide) + +## Getting started + +1. [Dump the schema](#dump-the-schema) +2. [Configure session/authencation](#sessions-and-authentication) +3. [Make queries](#make-queries) + +### Dump the schema +One of the main benefits of GraphQL is its [schema and type system](https://graphql.org/learn/schema/) +which enables tools like graphql-client to ensure your queries are valid in development. + +So the first step in making GraphQL queries is having a local JSON file of Shopify's Admin schema. +This gem provides a `shopify_api:graphql:dump` Rake task to make it as easy as possible: + +```bash +$ rake shopify_api:graphql:dump SHOP_URL="https://API_KEY:PASSWORD@SHOP_NAME.myshopify.com" API_VERSION=2020-01 +``` + +If successful `db/shopify_graphql_schemas/2020-01.json` will be created. + +You can either use private app authentication or an OAuth access token. Run `rake shopify_api:graphql:dump` +to see full usage details. + +If you're using shopify_api in a Rails app, the default location for schema files is `db/shopify_graphql_schemas`. +For non-Rails applications, the default is `shopify_graphql_schemas` in your project root. + +The schema path location can be changed via `ShopifyAPI::GraphQL.schema_location`: + +```ruby +ShopifyAPI::GraphQL.schema_location = 'assets/schemas' +``` + +#### Updating schemas +Each time you want to use a new API version, or update an existing one +(such as the `unstable` version), simply run the Rake task again to overwrite the file. + +### Sessions and authentication +The GraphQL client is designed to be integrated with the rest of shopify_api so +all its features such as sessions, authentication, and API versioning work the +exact same. + +If you've already been using the shopify_api gem in your application to make +REST API calls then no other configuration is necessary. + +Steps 1-5 of our main [Getting started](https://github.com/Shopify/shopify_api#getting-started) +section still apply for the GraphQL client as well. + +### Make queries +Now that you've dumped a schema file and configured an authenticated session, you can make GraphQL API requests. +graphql-client encourages all queries to be defined statically as constants: + +```ruby +SHOP_NAME_QUERY = ShopifyAPI::GraphQL.client.parse <<-'GRAPHQL' + { + shop { + name + } + } +GRAPHQL + +result = ShopifyAPI::GraphQL.client.query(SHOP_NAME_QUERY) +result.data.shop.name +``` + +But we've also enabled its `allow_dynamic_queries` option if you prefer: + +```ruby +query = ShopifyAPI::GraphQL.client.parse <<-'GRAPHQL' + { + shop { + name + } + } +GRAPHQL + +result = ShopifyAPI::GraphQL.client.query(query) +result.data.shop.name +``` + +See the [graphql-client documentation](https://github.com/github/graphql-client#defining-queries) +for more details on defining and executing queries. + +## Rails integration +`ShopifyAPI::GraphQL` integrates with Rails to automatically do the following: + +* load the `shopify_api:graphql:dump` Rake task +* set the `schema_location` to be in the `db` directory in your Rails root +* initialize clients in the Rails app initializer phase + +## API versioning +`ShopifyAPI::GraphQL` is version aware and lets you easily make queries to multiple +API versions through version specific clients if need be. + +If you have multiple clients and need to be explicit you can specify the version parameter: + +```ruby +ShopifyAPI::GraphQL.client # defaults to the client using ShopifyAPI::Base.api_version +ShopifyAPI::GraphQL.client('unstable') +``` + +## Initialization process +`ShopifyAPI::GraphQL` is a thin integration layer which initializes `GraphQL::Client`s +from local schema files. + +`ShopifyAPI::GraphQL.initialize_clients` scans `ShopifyAPI::GraphQL.schema_location` +and creates a client for each version specific schema file found. + +This happens automatically in a Rails application due to our [integration](#rails-integration). +For non-Rails applications, ensure you call `ShopifyAPI::GraphQL.initialize_clients` +during your boot process. + +The goal is to have all clients created at boot so there's no schema loading, +parsing, or client instantiation done during runtime when your app serves a request. + +## Migration guide +Prior to shopify_api v9.0 the GraphQL client implementation was limited and almost +unusable due to the client making dynamic introspection queries to Shopify's API. +This was not only very slow but also led to unbounded memory growth. + +There are two steps to migrate to the new client: +1. [Dump a local schema file](#dump-the-schema) +2. [Migrate `client` usage](#migrate-usage) + +### Migrate usage + +Previously a client was initialized with `ShopifyAPI::GraphQL.new`: +```ruby +client = ShopifyAPI::GraphQL.new + +SHOP_NAME_QUERY = client.parse <<-'GRAPHQL' + { + shop { + name + } + } +GRAPHQL + +result = client.query(SHOP_NAME_QUERY) +result.data.shop.name +``` + +Now there's no need to initialize a client so all references to +`ShopifyAPI::GraphQL.new` should be removed and instead the client is called +via `ShopifyAPI::GraphQL.client`: + +```ruby +client = ShopifyAPI::GraphQL.client + +SHOP_NAME_QUERY = client.parse <<-'GRAPHQL' + { + shop { + name + } + } +GRAPHQL + +result = client.query(SHOP_NAME_QUERY) +result.data.shop.name +``` + +See [making queries](#making-queries) for more usage details. diff --git a/lib/shopify_api/graphql/task.rake b/lib/shopify_api/graphql/task.rake index b5a84f94c..ee4969b7f 100644 --- a/lib/shopify_api/graphql/task.rake +++ b/lib/shopify_api/graphql/task.rake @@ -7,30 +7,62 @@ namespace :shopify_api do # add the Rails environment task as a prerequisite if loaded from a Rails app prereqs << :environment if Rake::Task.task_defined?('environment') - desc 'Writes the Shopify Admin API GraphQL schema to a local file' + desc 'Dumps a local JSON schema file of the Shopify Admin API' task dump: prereqs do - site_url = ENV['SITE_URL'] || ENV['site_url'] - shop_domain = ENV['SHOP_DOMAIN'] || ENV['shop_domain'] - api_version = ENV['API_VERSION'] || ENV['api_version'] + usage = <<~USAGE + + Usage: rake shopify_api:graphql:dump [] + + Dumps a local JSON schema file of the Shopify Admin API. The schema is specific to an + API version and authentication is required (either OAuth or private app). + + Dump the schema file for the 2020-01 API version using private app authentication: + $ rake shopify_api:graphql:dump SHOP_URL="https://API_KEY:PASSWORD@SHOP_NAME.myshopify.com" API_VERSION=2020-01 + + Dump the schema file for the unstable API version using an OAuth access token: + $ rake shopify_api:graphql:dump SHOP_DOMAIN=SHOP_NAME.myshopify.com ACCESS_TOKEN=abc API_VERSION=unstable + + See https://github.com/Shopify/shopify_api#getting-started for more + details on getting started with authenticated API calls. + + Arguments: + ACCESS_TOKEN OAuth access token (shop specific) + API_VERSION API version handle [example: 2020-01] + SHOP_DOMAIN Shop domain (without path) [example: SHOP_NAME.myshopify.com] + SHOP_URL Shop URL for private apps [example: https://API_KEY:PASSWORD@SHOP_NAME.myshopify.com] + USAGE + access_token = ENV['ACCESS_TOKEN'] || ENV['access_token'] + api_version = ENV['API_VERSION'] || ENV['api_version'] + shop_url = ENV['SHOP_URL'] || ENV['shop_url'] + shop_domain = ENV['SHOP_DOMAIN'] || ENV['shop_domain'] + + unless access_token || api_version || shop_url || shop_domain + puts usage + exit(1) + end - unless site_url || shop_domain - puts 'Either SHOP_DOMAIN or SITE_URL is required for authentication' + unless shop_url || shop_domain + puts 'Error: either SHOP_DOMAIN or SHOP_URL is required for authentication' + puts usage exit(1) end - if site_url && shop_domain - puts 'SHOP_DOMAIN and SITE_URL cannot be used together. Use one or the other for authentication.' + if shop_url && shop_domain + puts 'Error: SHOP_DOMAIN and SHOP_URL cannot be used together. Use one or the other for authentication.' + puts usage exit(1) end if shop_domain && !access_token - puts 'ACCESS_TOKEN required when SHOP_DOMAIN is used' + puts 'Error: ACCESS_TOKEN required when SHOP_DOMAIN is used' + puts usage exit(1) end unless api_version - puts "API_VERSION required. Example `2020-01`" + puts 'Error: API_VERSION required. Example: 2020-01' + puts usage exit(1) end @@ -40,8 +72,8 @@ namespace :shopify_api do shopify_session = ShopifyAPI::Session.new(domain: shop_domain, token: access_token, api_version: api_version) ShopifyAPI::Base.activate_session(shopify_session) - if site_url - ShopifyAPI::Base.site = site_url + if shop_url + ShopifyAPI::Base.site = shop_url end schema_location = ShopifyAPI::GraphQL.schema_location