-
Notifications
You must be signed in to change notification settings - Fork 466
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is a complete rewrite of the GraphQL client functionality. The previous implementation suffered from a few problems making it virtually unusable as detailed in #511. Here's a summary: 1. you couldn't specify a local schema file 2. due to the above, the client made a dynamic introspection request on initialization which was very slow 3. unbounded memory growth due to building new clients at runtime This rewrite was focused on solving those problems first and foremost but we also had a few other goals: * support API versioning * provide better defaults and an improved developer experience * ensure it's impossible to do the wrong thing The new GraphQL client *only* supports loading local schema files to ensure no introspection requests are made during app runtime. The goal is that clients are fully initialized at application boot time (and if you're using Rails this is handled automatically). Workflow: 1. Set `ShopifyAPI::GraphQL.schema_location` to a directory path (or use the default in Rails of `db/shopify_graphql_schemas`). 2. Save a JSON version of Shopify's Admin schema locally (or use the `shopify_api:graphql:dump` Rake task) to the `schema_location` and name it after the API version: `2020-01.json`. 3. Access the client at `ShopifyAPI::GraphQL.client` 4. Execute queries via `client.query`
- Loading branch information
1 parent
2818c3b
commit 45dd353
Showing
12 changed files
with
2,531 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# frozen_string_literal: true | ||
require 'graphql/client' | ||
require 'shopify_api/graphql/http_client' | ||
|
||
module ShopifyAPI | ||
module GraphQL | ||
DEFAULT_SCHEMA_LOCATION_PATH = Pathname('shopify_graphql_schemas') | ||
|
||
InvalidSchema = Class.new(StandardError) | ||
InvalidSchemaLocation = Class.new(StandardError) | ||
InvalidClient = Class.new(StandardError) | ||
|
||
class << self | ||
delegate :parse, :query, to: :client | ||
|
||
def client(api_version = ShopifyAPI::Base.api_version.handle) | ||
initialize_client_cache | ||
cached_client = @_client_cache[api_version] | ||
|
||
if cached_client | ||
cached_client | ||
else | ||
schema_file = schema_location.join("#{api_version}.json") | ||
|
||
if !schema_file.exist? | ||
raise InvalidClient, <<~MSG | ||
Client for API version #{api_version} does not exist because no schema file exists at `#{schema_file}`. | ||
To dump the schema file, use the `rake shopify_api:graphql:dump` task. | ||
MSG | ||
else | ||
puts '[WARNING] Client was not pre-initialized. Ensure `ShopifyAPI::GraphQL.initialize_clients` is called during app initialization.' | ||
initialize_clients | ||
@_client_cache[api_version] | ||
end | ||
end | ||
end | ||
|
||
def clear_clients | ||
@_client_cache = {} | ||
end | ||
|
||
def initialize_clients | ||
initialize_client_cache | ||
|
||
Dir.glob(schema_location.join("*.json")).each do |schema_file| | ||
schema_file = Pathname(schema_file) | ||
matches = schema_file.basename.to_s.match(/^#{ShopifyAPI::ApiVersion::HANDLE_FORMAT}\.json$/) | ||
|
||
if matches | ||
api_version = ShopifyAPI::ApiVersion.new(handle: matches[1]) | ||
else | ||
raise InvalidSchema, "Invalid schema file name `#{schema_file}`. Does not match format of: `<version>.json`." | ||
end | ||
|
||
schema = ::GraphQL::Client.load_schema(schema_file.to_s) | ||
client = ::GraphQL::Client.new(schema: schema, execute: HTTPClient.new(api_version)).tap do |c| | ||
c.allow_dynamic_queries = true | ||
end | ||
|
||
@_client_cache[api_version.handle] = client | ||
end | ||
end | ||
|
||
def schema_location | ||
@schema_location || DEFAULT_SCHEMA_LOCATION_PATH | ||
end | ||
|
||
def schema_location=(path) | ||
@schema_location = Pathname(path) | ||
end | ||
|
||
private | ||
|
||
def initialize_client_cache | ||
@_client_cache ||= {} | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# frozen_string_literal: true | ||
require 'graphql/client/http' | ||
|
||
module ShopifyAPI | ||
module GraphQL | ||
class HTTPClient < ::GraphQL::Client::HTTP | ||
def initialize(api_version) | ||
@api_version = api_version | ||
end | ||
|
||
def headers(_context) | ||
ShopifyAPI::Base.headers | ||
end | ||
|
||
def uri | ||
ShopifyAPI::Base.site.dup.tap do |uri| | ||
uri.path = @api_version.construct_graphql_path | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# frozen_string_literal: true | ||
require 'rails/railtie' | ||
|
||
module ShopifyAPI | ||
module GraphQL | ||
class Railtie < Rails::Railtie | ||
initializer 'shopify_api.initialize_graphql_clients' do |app| | ||
ShopifyAPI::GraphQL.schema_location = app.root.join('db', ShopifyAPI::GraphQL.schema_location) | ||
ShopifyAPI::GraphQL.initialize_clients | ||
end | ||
|
||
rake_tasks do | ||
load 'shopify_api/graphql/task.rake' | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# frozen_string_literal: true | ||
require 'fileutils' | ||
|
||
namespace :shopify_api do | ||
namespace :graphql do | ||
prereqs = [] | ||
# 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' | ||
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'] | ||
access_token = ENV['ACCESS_TOKEN'] || ENV['access_token'] | ||
|
||
unless site_url || shop_domain | ||
puts 'Either SHOP_DOMAIN or SITE_URL is required for authentication' | ||
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.' | ||
exit(1) | ||
end | ||
|
||
if shop_domain && !access_token | ||
puts 'ACCESS_TOKEN required when SHOP_DOMAIN is used' | ||
exit(1) | ||
end | ||
|
||
unless api_version | ||
puts "API_VERSION required. Example `2020-01`" | ||
exit(1) | ||
end | ||
|
||
ShopifyAPI::ApiVersion.fetch_known_versions | ||
ShopifyAPI::ApiVersion.version_lookup_mode = :raise_on_unknown | ||
|
||
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 | ||
end | ||
|
||
schema_location = ShopifyAPI::GraphQL.schema_location | ||
FileUtils.mkdir_p(schema_location) unless Dir.exist?(schema_location) | ||
|
||
client = ShopifyAPI::GraphQL::HTTPClient.new(ShopifyAPI::Base.api_version.handle) | ||
GraphQL::Client.dump_schema(client, schema_location.join("#{api_version}.json").to_s) | ||
end | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.