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?
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::Directivemodel
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.
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.
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.
Most helpful comment
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.