yuki24/artemis

Dynamic subdomain in endpoint

nicklloyd opened this issue ยท 11 comments

Hi there - first of all, thanks for your work on this gem!

I'm looking at a current requirement of being able to have a client that is responsible for calling Shopify's Graph, but in a way that the url can be passed in as a variable.

The schema remains the same as it's only for Shopify, but they use subdomains to identify the shop you are calling...

@JanStevens and I have been trading ideas, but were hoping you'd have some insight into the best/cleanest way to approach.

Cheers!

Thanks for using Artemis!

How many subdomains are you dealing with? Is it just a handful you could hard-code, or you have to programmatically switch endpoints? If former, you could define multiple GraphQL endpoints in the config/graphql.yml:

default: &default
  ...
  # some other config
  ...

  schema_path: path/to/schema_for_shopify.json

development:
  shop1:
    <<: *default
    url: https://shop1.myshopify.com/

  shop2:
    <<: *default
    url: https://shop2.myshopify.com/

  shop3:
    <<: *default
    url: https://shop3.myshopify.com/

test:
  ...

Then you should be able to use Shop1, shop2, and shop3.

If latter, you would probably monkey-patch Artemis. Perhaps you could add a scope to the connection object using a hash in the GraphQLEndpoint class:

def connection
@connection || @mutex_for_connection.synchronize do
@connection ||= ::Artemis::Adapters.lookup(adapter).new(url, service_name: name, timeout: timeout, pool_size: pool_size)
end
end

And the Client should pick up the right connection in here:

def connection(context = {})
Executor.new(endpoint.connection, callbacks, default_context.deep_merge(context))
end

Does that helpful? Let me know if it helps solve your problem.

If it's a public Shopify "app", the amount of shops is dynamic and ~endless. (Basically any shop that installs the app)

Also, Shopify's GraphQL root type is called QueryRoot, while Artemis looks for Query. I guess this could be fetched from the schema, instead of hard-coded.

Another issue is that each shop has its own access token. How would I pass that into Artemis call?

I love Artemis' API and the focus on test support! But these couple of things make it impossible for me to use it. Let me know if you know any ways around these. Thanks!

@vfonic Would you expect a single GraphQL client in that case? For example, if there are surf-shop and chocolate-shop, would you expect to switch the endpoint by:

Shopify.with_context(shop: "surf-shop").some_graphql_query(some: 'args')
Shopify.with_context(shop: "chocolate-shop").some_graphql_query(some: 'args')

Or would you expect a dynamically defined constant for each shop?

SurfShop.some_graphql_query(some: 'args')
ChocolateShop.some_graphql_query(some: 'args')

I'm assuming the shop id comes from a database, in which case the latter might be tricky to implement.

Great question!

Shopify made it quite seamless as you can just do:

Shop.with_shopify_session do
#...
end

Another option is to "activate" session for the shop. That's what I'd actually prefer. Something along the lines of:

# Shopify is Artemis client here
Shopify.authenticate

Shopify.products(...) 

Basically anything that allows me to authenticate once, kinda like how you set the locale or time zone. This is because for a single request, I'll always query stuff on a single shop. If I need to iterate over all the shops in background job, I'll call authenticate within the loop.

Thinking more about it, maybe this could be an extension gem on top of Artemis? It doesn't feel like it should be a part of the gem itself.

I'm willing to help what I can to make this happen.

Shopify.authenticate

This call implies that it would mutate a global state, which makes it difficult to make it thread-safe. A safer way would be:

surf_shop = Shopify.authenticate_with("surf-shop")

surf_shop.products(...)

Or:

Shopify.authenticate_with("surf-shop") do |surf_shop|
  surf_shop.do_stuff
  surf_shop.other_stuff
end

So the endpoint toggle would be scoped to the surf_shop object. Otherwise, we need to lock execution by other threads. How does it look to you?

Also, how does Shop.with_shopify_session { ... } know which subdomain to establish a connection to without any identifiers?

We could definitely start with a plugin, like artemis-multidomain or even artemis-shopify.

Ah, I wrote Shop.with_shopify_session, while I meant shop.with_shopify_session, as an instance variable of Shop model (which has shopify_domain column).

surf_shop = Shopify.authenticate_with("surf-shop")

I love this! My idea is to create a helper function that I can call wherever. Something along the lines of this:

def graphql_shop_client
  # current_shop is currently authenticated shop for which
  # the request is being processed
  Shopify.authenticate_with(current_shop.shopify_domain)
end

I assume Shopify.authenticate_with(current_shop.shopify_domain) won't make any API calls, but just set the correct domain.

Awesome, I think I have enough information to spike on a multi-domain client. I'll see what I can do this weekend.

Hey @yuki24! Thanks for adding this functionality!

Is there any documentation where I could read more how to use the multi-domain functionality?

Thank you!

@vfonic I'm looking to add documentation around it this weekend.

Awesome! Thank you so much! :)

I'd love to give it a try with my Shopify app.

I have just added a little section for multi domain. It would be greatly appreciated if you could provide feedback.