This gem extends the GraphQL Ruby gem to add support for creating an Apollo Federation schema.
Add this line to your application's Gemfile:
gem 'apollo-federation'And then execute:
$ bundle Or install it yourself as:
$ gem install apollo-federation Include the ApolloFederation::Argument module in your base argument class:
class BaseArgument < GraphQL::Schema::Argument include ApolloFederation::Argument endInclude the ApolloFederation::Field module in your base field class:
class BaseField < GraphQL::Schema::Field include ApolloFederation::Field argument_class BaseArgument endInclude the ApolloFederation::Object module in your base object class:
class BaseObject < GraphQL::Schema::Object include ApolloFederation::Object field_class BaseField endInclude the ApolloFederation::Interface module in your base interface module:
module BaseInterface include GraphQL::Schema::Interface include ApolloFederation::Interface field_class BaseField endInclude the ApolloFederation::Union module in your base union class:
class BaseUnion < GraphQL::Schema::Union include ApolloFederation::Union endInclude the ApolloFederation::EnumValue module in your base enum value class:
class BaseEnumValue < GraphQL::Schema::EnumValue include ApolloFederation::EnumValue endInclude the ApolloFederation::Enum module in your base enum class:
class BaseEnum < GraphQL::Schema::Enum include ApolloFederation::Enum enum_value_class BaseEnumValue endInclude the ApolloFederation::InputObject module in your base input object class:
class BaseInputObject < GraphQL::Schema::InputObject include ApolloFederation::InputObject argument_class BaseArgument endInclude the ApolloFederation::Scalar module in your base scalar class:
class BaseScalar < GraphQL::Schema::Scalar include ApolloFederation::Scalar endFinally, include the ApolloFederation::Schema module in your schema:
class MySchema < GraphQL::Schema include ApolloFederation::Schema endOptional: To opt in to Federation v2, specify the version in your schema:
class MySchema < GraphQL::Schema include ApolloFederation::Schema federation version: '2.0' endThe example folder contains a Ruby implementation of Apollo's federation-demo. To run it locally, install the Ruby dependencies:
$ bundle Install the Node dependencies:
$ yarn Start all of the services:
$ yarn start-services Start the gateway:
$ yarn start-gateway This will start up the gateway and serve it at http://localhost:5000.
The API is designed to mimic the API of Apollo's federation library.
It's best to read and understand the way federation works, in general, before attempting to use this library.
Call extend_type within your class definition:
class User < BaseObject extend_type endCall key within your class definition:
class User < BaseObject key fields: :id endCompound keys are also supported:
class User < BaseObject key fields: [:id, { organization: :id }] endAs well as non-resolvable keys:
class User < BaseObject key fields: :id, resolvable: false endSee field set syntax for more details on the format of the fields option.
Pass the external: true option to your field definition:
class User < BaseObject field :id, ID, null: false, external: true endPass the requires: option to your field definition:
class Product < BaseObject field :price, Int, null: true, external: true field :weight, Int, null: true, external: true field :shipping_estimate, Int, null: true, requires: { fields: [:price, :weight] } endSee field set syntax for more details on the format of the fields option.
Pass the provides: option to your field definition:
class Review < BaseObject field :author, 'User', null: true, provides: { fields: :username } endSee field set syntax for more details on the format of the fields option.
Call shareable within your class definition:
class User < BaseObject shareable endPass the shareable: true option to your field definition:
class User < BaseObject field :id, ID, null: false, shareable: true endCall inaccessible within your class definition:
class User < BaseObject inaccessible endPass the inaccessible: true option to your field definition:
class User < BaseObject field :id, ID, null: false, inaccessible: true endPass the override: option to your field definition:
class Product < BaseObject field :id, ID, null: false field :inStock, Boolean, null: false, override: { from: 'Products' } endCall tag within your class definition:
class User < BaseObject tag name: 'private' endPass the tags: option to your field definition:
class User < BaseObject field :id, ID, null: false, tags: [{ name: 'private' }] endField sets can be either strings encoded with the Apollo Field Set syntax or arrays, hashes and snake case symbols that follow the graphql-ruby conventions:
# Equivalent to the "organizationId" field set :organization_id # Equivalent to the "price weight" field set [:price, :weight] # Equivalent to the "id organization { id }" field set [:id, { organization: :id }]Define a resolve_reference class method on your object. The method will be passed the reference from another service and the context for the query.
class User < BaseObject key fields: :user_id field :user_id, ID, null: false def self.resolve_reference(reference, context) USERS.find { |user| user[:userId] == reference[:userId] } end endTo maintain backwards compatibility, by default, reference hash keys are camelcase. They can be underscored by setting underscore_reference_keys on your entity class. In order to maintain consistency with GraphQL Ruby, we may change the keys to be underscored by default in a future major release.
class User < BaseObject key fields: :user_id field :user_id, ID, null: false underscore_reference_keys true def self.resolve_reference(reference, context) USERS.find { |user| user[:user_id] == reference[:user_id] } end endAlternatively you can change the default for your project by setting underscore_reference_keys on BaseObject:
class BaseObject < GraphQL::Schema::Object include ApolloFederation::Object field_class BaseField underscore_reference_keys true endTo support federated tracing:
- Add
trace_with ApolloFederation::Tracing::Tracerto your schema class. - Change your controller to add
tracing_enabled: trueto the execution context based on the presence of the "include trace" header:def execute # ... context = { # Pass in the headers from your web framework. For Rails this will be request.headers # but for other frameworks you can pass the Rack env. tracing_enabled: ApolloFederation::Tracing.should_add_traces(request.headers) } # ... end
When using tools like rover for schema validation, etc., add a Rake task that prints the Federated SDL to a file:
namespace :graphql do namespace :federation do task :dump do File.write("schema.graphql", MySchema.federation_sdl) end end endExample validation check with Rover and Apollo Studio:
bin/rake graphql:federation:dump rover subgraph check mygraph@current --name mysubgraph --schema schema.graphqlThis library does not include any testing helpers currently. A federated service receives subgraph queries from the Apollo Gateway via the _entities field and that can be tested in a request spec.
With Apollo Gateway setup to hit your service locally or by using existing query logs, you can retrieve the generated _entities queries.
For example, if you have a blog service that exposes posts by a given author, the query received by the service might look like this.
query($representations: [_Any!]!) { _entities(representations: $representations) { ... on BlogPost { id title body } } }Where $representations is an array of entity references from the gateway.
{ "representations": [ { "__typename": "BlogPost", "id": 1 }, { "__typename": "BlogPost", "id": 2 } ] }Using RSpec as an example, a request spec for this query.
it "resolves the blog post entities" do blog_post = BlogPost.create!(attributes) query = <<~GRAPHQL query($representations: [_Any!]!) { _entities(representations: $representations) { ... on BlogPost { id title body } } } GRAPHQL variables = { representations: [{ __typename: "BlogPost", id: blog_post.id }] } result = Schema.execute(query, variables: variables) expect(result.dig("data", "_entities", 0, "id")).to eq(blog_post.id) endSee discussion at #74 and an internal spec that resolves _entities for more details.
- For GraphQL older than 1.12, the interpreter runtime has to be used.
- Does not add directives to the output of
Schema.to_definition. Sincegraphql-rubydoesn't natively support schema directives, the directives will only be visible to the Apollo Gateway through theQuery._servicefield (see the Apollo Federation specification) or viaSchema#federation_sdlas explained above.
Gusto GraphQL Team: