• Stars
    star
    216
  • Rank 183,179 (Top 4 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 5 years ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Ruby implementation of Apollo Federation

apollo-federation

CircleCI

This gem extends the GraphQL Ruby gem to add support for creating an Apollo Federation schema.

Installation

Add this line to your application's Gemfile:

gem 'apollo-federation'

And then execute:

$ bundle

Or install it yourself as:

$ gem install apollo-federation

Getting Started

Include the ApolloFederation::Argument module in your base argument class:

class BaseArgument < GraphQL::Schema::Argument
  include ApolloFederation::Argument
end

Include the ApolloFederation::Field module in your base field class:

class BaseField < GraphQL::Schema::Field
  include ApolloFederation::Field

  argument_class BaseArgument
end

Include the ApolloFederation::Object module in your base object class:

class BaseObject < GraphQL::Schema::Object
  include ApolloFederation::Object

  field_class BaseField
end

Include the ApolloFederation::Interface module in your base interface module:

module BaseInterface
  include GraphQL::Schema::Interface
  include ApolloFederation::Interface

  field_class BaseField
end

Include the ApolloFederation::Union module in your base union class:

class BaseUnion < GraphQL::Schema::Union
  include ApolloFederation::Union
end

Include the ApolloFederation::EnumValue module in your base enum value class:

class BaseEnumValue < GraphQL::Schema::EnumValue
  include ApolloFederation::EnumValue
end

Include the ApolloFederation::Enum module in your base enum class:

class BaseEnum < GraphQL::Schema::Enum
  include ApolloFederation::Enum

  enum_value_class BaseEnumValue
end

Include the ApolloFederation::InputObject module in your base input object class:

class BaseInputObject < GraphQL::Schema::InputObject
  include ApolloFederation::InputObject

  argument_class BaseArgument
end

Include the ApolloFederation::Scalar module in your base scalar class:

class BaseScalar < GraphQL::Schema::Scalar
  include ApolloFederation::Scalar
end

Finally, include the ApolloFederation::Schema module in your schema:

class MySchema < GraphQL::Schema
  include ApolloFederation::Schema
end

Optional: To opt in to Federation v2, specify the version in your schema:

class MySchema < GraphQL::Schema
  include ApolloFederation::Schema
  federation version: '2.0'
end

Example

The 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.

Usage

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.

Extending a type

Apollo documentation

Call extend_type within your class definition:

class User < BaseObject
  extend_type
end

The @key directive

Apollo documentation

Call key within your class definition:

class User < BaseObject
  key fields: :id
end

Compound keys are also supported:

class User < BaseObject
  key fields: [:id, { organization: :id }]
end

As well as non-resolvable keys:

class User < BaseObject
  key fields: :id, resolvable: false
end

See field set syntax for more details on the format of the fields option.

The @external directive

Apollo documentation

Pass the external: true option to your field definition:

class User < BaseObject
  field :id, ID, null: false, external: true
end

The @requires directive

Apollo documentation

Pass 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] }
end

See field set syntax for more details on the format of the fields option.

The @provides directive

Apollo documentation

Pass the provides: option to your field definition:

class Review < BaseObject
  field :author, 'User', null: true, provides: { fields: :username }
end

See field set syntax for more details on the format of the fields option.

The @shareable directive (Apollo Federation v2)

Apollo documentation

Call shareable within your class definition:

class User < BaseObject
  shareable
end

Pass the shareable: true option to your field definition:

class User < BaseObject
  field :id, ID, null: false, shareable: true
end

The @inaccessible directive (Apollo Federation v2)

Apollo documentation

Call inaccessible within your class definition:

class User < BaseObject
  inaccessible
end

Pass the inaccessible: true option to your field definition:

class User < BaseObject
  field :id, ID, null: false, inaccessible: true
end

The @override directive (Apollo Federation v2)

Apollo documentation

Pass the override: option to your field definition:

class Product < BaseObject
  field :id, ID, null: false
  field :inStock, Boolean, null: false, override: { from: 'Products' }
end

The @tag directive (Apollo Federation v2)

Apollo documentation

Call tag within your class definition:

class User < BaseObject
  tag name: 'private'
end

Pass the tags: option to your field definition:

class User < BaseObject
  field :id, ID, null: false, tags: [{ name: 'private' }]
end

Field set syntax

Field 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 }]

Reference resolvers

Apollo documentation

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
end

To 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
end

Alternatively 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
end

Tracing

To support federated tracing:

  1. Add use ApolloFederation::Tracing to your schema class.
  2. Change your controller to add tracing_enabled: true to 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

Exporting the Federated SDL

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
end

Example validation check with Rover and Apollo Studio:

bin/rake graphql:federation:dump
rover subgraph check mygraph@current --name mysubgraph --schema schema.graphql

Testing the federated schema

This 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)
end

See discussion at #74 and an internal spec that resolves _entities for more details.

Known Issues and Limitations

  • For GraphQL older than 1.12, the interpreter runtime has to be used.
  • Does not add directives to the output of Schema.to_definition. Since graphql-ruby doesn't natively support schema directives, the directives will only be visible to the Apollo Gateway through the Query._service field (see the Apollo Federation specification) or via Schema#federation_sdl as explained above.

Maintainers

More Repositories

1

it-cpe-opensource

Tools used by the CPE team at Gusto to manage our endpoints and software deployment systems.
Ruby
59
star
2

ar-query-matchers

Ruby test matchers for instrumenting ActiveRecord query counts
Ruby
54
star
3

buildkite-builder

A Ruby DSL for programmatically creating Buildkite pipelines.
Ruby
49
star
4

grpc-web-ruby

Host gRPC-Web endpoints for Ruby gRPC services in a Rack or Rails app (over HTTP/1.1). Client included.
Ruby
32
star
5

committer

A CLI for managing linter executions in git hooks
Go
23
star
6

react-masked-field

A masked input component built in React
TypeScript
19
star
7

zen-dsl-tutorial

Repository for following along with a DSL Tutorial.
Ruby
14
star
8

semantic-release-rubygem

A semantic-release plugin for publishing Ruby gems
JavaScript
9
star
9

deprecation_helper

This is a simple, low-dependency gem for managing deprecations.
Ruby
6
star
10

contract_value_object

Validate that your objects have the right inputs.
Ruby
6
star
11

omniauth-gusto

OmniAuth strategy for Gusto
Ruby
5
star
12

ubuntu-eks-ami

Shell
4
star
13

gusto.github.io

Api Documentation
HTML
4
star
14

ruby

Docker images for ruby (and ubuntu)
Dockerfile
3
star
15

aptible-cli-docker

Simple container containing the aptible cli
Dockerfile
3
star
16

cjsx-converter

Convert Coffee/CJSX into pretty JS/JSX
JavaScript
3
star
17

dom-to-image

Generates an image from a DOM node using HTML5 canvas
JavaScript
2
star
18

greenhouse_api

An api client for working with the Greenhouse Harvest API
Ruby
2
star
19

eslint-config-gusto

A shared ESLint config for Gusto's JS projects.
JavaScript
2
star
20

homebrew-gusto

Homebrew packages for Gusto
Ruby
2
star
21

aptible_datadog_postgres_monitor

A docker container for Aptible containing the data dog agent to watch and provide metrics on Postgres instances.
Python
2
star
22

validate-rb

Yummy constraint validations for Ruby
Gherkin
1
star
23

keysmith

Ruby
1
star
24

rspec-retryable

Adds ability to fully control RSpec retrying
Ruby
1
star
25

buildkite-cli

Delicious CLI for snacking on BuildKite, like `gh` for GitHub 🍔
Ruby
1
star
26

webpacker

Cloned from https://github.com/rails/webpacker
Ruby
1
star
27

explicit_activerecord

This gem helps you use ActiveRecord more explicitly.
Ruby
1
star