rmosolgo / graphql-ruby

Ruby implementation of GraphQL
http://graphql-ruby.org
MIT License
5.38k stars 1.39k forks source link

Adding schema directives #2318

Closed rylanc closed 3 years ago

rylanc commented 5 years ago

I'm currently working on an implementation of Apollo Federation for graphql-ruby and part of that specification includes supporting schema directives:

extend type Product @key(fields: "upc") {
  upc: String! @external
  weight: Int @external
  price: Int @external
  inStock: Boolean
  shippingEstimate: Int @requires(fields: "price weight")
}

It would make the federation work much easier if graphql-ruby supported schema directives. The federation directives don't actually modify an resolution behavior, they're only used to output an SDL string with the defined directives (like above). It looks like GraphQL::Language::Printer contains support for printing directives, but there's currently no way to define a directive on an object (without using Schema.from_definition).

It would be simple enough to add support for declaring schema directives and attaching them to an object or field, but I'd like to avoid adding an API that might not be useful/intuitive when/if support for modifying resolution behavior was added. I've been looking over how other server implementations define schema directives (e.g. Apollo Server) and was wondering if you (@rmosolgo) have given any thought to the interface for schema directives. The Apollo approach seems very flexible, but probably not all that feasible in graphql-ruby. Also, if I were to attempt to implement it, I'm having a hard time imagining how it would fit in with the current Schema::Directive model (which is limited to query directives).

Another angle would be to go with a schema first approach (through Schema.from_definition), but that doesn't seem to be the recommended way to build a schema. What are your thoughts on the future of schema first in graphql-ruby?

rmosolgo commented 5 years ago

Thanks for starting this discussion!

no way to define a directive on an object (without using Schema.from_definition)

It seems like you're talking about directives without the SDL. What is a directive outside of the SDL?

I think it's just metadata attached to parts of a GraphQL Schema. Since the schema is a class, and types are classes, and fields and arguments are instances of their classes, you can attach metadata to them using instance variables (plain ol' Ruby). For example:

class Types::BaseField < GraphQL::Schema::Field 
  # Should this be `@external` for Apollo Federation? 
  attr_reader :external 

  def initialize(*args, external: false, **kwargs, &block)
    @external = external # capture metadata 
    super(*args, **kwargs, &block)
  end 
end 

That way, fields could be defined with field(..., external: true). @requires(...) could be implemented the same way.

Would that help?

the current Schema::Directive model

You're right, it's for runtime directives only.

the future of schema first

Personally I don't have a use-case for it but I know plenty of companies use it exclusively. So it's certainly not going anywhere, but I don't actively develop it.

RomainEndelin commented 5 years ago

That's true, field metadata do the same as a schema directive. The only difference is that a schema directive should transpile into the generated SDL, whereas metadata remain internal to Ruby definition.

Schema directives could be useful for introspection on a client's side.

gmac commented 3 years ago

I've setup a gem with a generic implementation of schema directives here: https://github.com/gmac/graphql-schema-directives-ruby. It lacks the opinions of the apollo federation gem, and just focuses on defining/printing schema directives for your own unique purposes.

rmosolgo commented 3 years ago

Hey, thanks for sharing your work on this. I should have followed up here -- GraphQL-Ruby 1.12 will support schema directives, too (#3224).