Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Gusto/apollo-federation-ruby
A Ruby implementation of Apollo Federation
https://github.com/Gusto/apollo-federation-ruby
apollographql graphql ruby
Last synced: 3 months ago
JSON representation
A Ruby implementation of Apollo Federation
- Host: GitHub
- URL: https://github.com/Gusto/apollo-federation-ruby
- Owner: Gusto
- License: mit
- Created: 2019-06-20T20:57:37.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2024-04-02T21:15:11.000Z (7 months ago)
- Last Synced: 2024-04-14T07:25:51.164Z (7 months ago)
- Topics: apollographql, graphql, ruby
- Language: Ruby
- Homepage:
- Size: 1.76 MB
- Stars: 215
- Watchers: 19
- Forks: 65
- Open Issues: 26
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# apollo-federation
[![CircleCI](https://circleci.com/gh/Gusto/apollo-federation-ruby/tree/main.svg?style=svg)](https://circleci.com/gh/Gusto/apollo-federation-ruby/tree/main)
This gem extends the [GraphQL Ruby](http://graphql-ruby.org/) gem to add support for creating an [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction/) schema.
## Installation
Add this line to your application's Gemfile:
```ruby
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:
```ruby
class BaseArgument < GraphQL::Schema::Argument
include ApolloFederation::Argument
end
```Include the `ApolloFederation::Field` module in your base field class:
```ruby
class BaseField < GraphQL::Schema::Field
include ApolloFederation::Fieldargument_class BaseArgument
end
```Include the `ApolloFederation::Object` module in your base object class:
```ruby
class BaseObject < GraphQL::Schema::Object
include ApolloFederation::Objectfield_class BaseField
end
```Include the `ApolloFederation::Interface` module in your base interface module:
```ruby
module BaseInterface
include GraphQL::Schema::Interface
include ApolloFederation::Interfacefield_class BaseField
end
```Include the `ApolloFederation::Union` module in your base union class:
```ruby
class BaseUnion < GraphQL::Schema::Union
include ApolloFederation::Union
end
```Include the `ApolloFederation::EnumValue` module in your base enum value class:
```ruby
class BaseEnumValue < GraphQL::Schema::EnumValue
include ApolloFederation::EnumValue
end
```Include the `ApolloFederation::Enum` module in your base enum class:
```ruby
class BaseEnum < GraphQL::Schema::Enum
include ApolloFederation::Enumenum_value_class BaseEnumValue
end
```Include the `ApolloFederation::InputObject` module in your base input object class:
```ruby
class BaseInputObject < GraphQL::Schema::InputObject
include ApolloFederation::InputObjectargument_class BaseArgument
end
```Include the `ApolloFederation::Scalar` module in your base scalar class:
```ruby
class BaseScalar < GraphQL::Schema::Scalar
include ApolloFederation::Scalar
end
```Finally, include the `ApolloFederation::Schema` module in your schema:
```ruby
class MySchema < GraphQL::Schema
include ApolloFederation::Schema
end
```**Optional:** To opt in to Federation v2, specify the version in your schema:
```ruby
class MySchema < GraphQL::Schema
include ApolloFederation::Schema
federation version: '2.0'
end
```## Example
The [`example`](./example/) folder contains a Ruby implementation of Apollo's [`federation-demo`](https://github.com/apollographql/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](https://www.apollographql.com/docs/apollo-server/federation/introduction/). It's best to read and understand the way federation works, in general, before attempting to use this library.
### Extending a type
[Apollo documentation](https://www.apollographql.com/docs/apollo-server/federation/core-concepts/#extending-external-types)
Call `extend_type` within your class definition:
```ruby
class User < BaseObject
extend_type
end
```### The `@key` directive
[Apollo documentation](https://www.apollographql.com/docs/apollo-server/federation/core-concepts/#entities-and-keys)
Call `key` within your class definition:
```ruby
class User < BaseObject
key fields: :id
end
```Compound keys are also supported:
```ruby
class User < BaseObject
key fields: [:id, { organization: :id }]
end
```As well as non-resolvable keys:
```ruby
class User < BaseObject
key fields: :id, resolvable: false
end
```See [field set syntax](#field-set-syntax) for more details on the format of the `fields` option.
### The `@external` directive
[Apollo documentation](https://www.apollographql.com/docs/apollo-server/federation/core-concepts/#referencing-external-types)
Pass the `external: true` option to your field definition:
```ruby
class User < BaseObject
field :id, ID, null: false, external: true
end
```### The `@requires` directive
[Apollo documentation](https://www.apollographql.com/docs/apollo-server/federation/advanced-features/#computed-fields)
Pass the `requires:` option to your field definition:
```ruby
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](#field-set-syntax) for more details on the format of the `fields` option.
### The `@provides` directive
[Apollo documentation](https://www.apollographql.com/docs/apollo-server/federation/advanced-features/#using-denormalized-data)
Pass the `provides:` option to your field definition:
```ruby
class Review < BaseObject
field :author, 'User', null: true, provides: { fields: :username }
end
```
See [field set syntax](#field-set-syntax) for more details on the format of the `fields` option.### The `@shareable` directive (Apollo Federation v2)
[Apollo documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives/#shareable)
Call `shareable` within your class definition:
```ruby
class User < BaseObject
shareable
end
```Pass the `shareable: true` option to your field definition:
```ruby
class User < BaseObject
field :id, ID, null: false, shareable: true
end
```### The `@inaccessible` directive (Apollo Federation v2)
[Apollo documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives/#inaccessible)
Call `inaccessible` within your class definition:
```ruby
class User < BaseObject
inaccessible
end
```Pass the `inaccessible: true` option to your field definition:
```ruby
class User < BaseObject
field :id, ID, null: false, inaccessible: true
end
```### The `@override` directive (Apollo Federation v2)
[Apollo documentation](https://www.apollographql.com/docs/federation/federated-types/federated-directives/#override)
Pass the `override:` option to your field definition:
```ruby
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](https://www.apollographql.com/docs/federation/federated-types/federated-directives/#tag)
Call `tag` within your class definition:
```ruby
class User < BaseObject
tag name: 'private'
end
```Pass the `tags:` option to your field definition:
```ruby
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]((https://www.apollographql.com/docs/apollo-server/federation/federation-spec/#scalar-_fieldset)) or arrays, hashes and snake case symbols that follow the graphql-ruby conventions:
```ruby
# 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](https://www.apollographql.com/docs/apollo-server/api/apollo-federation/#__resolvereference)
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.
```ruby
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.
```ruby
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`:```ruby
class BaseObject < GraphQL::Schema::Object
include ApolloFederation::Objectfield_class BaseField
underscore_reference_keys true
end
```### Tracing
To support [federated tracing](https://www.apollographql.com/docs/apollo-server/federation/metrics/):
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:
```ruby
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](https://www.apollographql.com/docs/rover/) for schema validation, etc., add a Rake task that prints the Federated SDL to a file:
```rb
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:
```sh
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.
```graphql
query($representations: [_Any!]!) {
_entities(representations: $representations) {
... on BlogPost {
id
title
body
}
}
}
```Where `$representations` is an array of entity references from the gateway.
```JSON
{
"representations": [
{
"__typename": "BlogPost",
"id": 1
},
{
"__typename": "BlogPost",
"id": 2
}
]
}
```Using RSpec as an example, a request spec for this query.
```ruby
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
}
}
}
GRAPHQLvariables = { 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](https://github.com/Gusto/apollo-federation-ruby/issues/74) and an [internal spec that resolves \_entities](https://github.com/Gusto/apollo-federation-ruby/blob/1d3baf4f8efcd02e7bf5bc7e3fee5b4fb963cd25/spec/apollo-federation/entities_field_spec.rb#L164) 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](https://www.apollographql.com/docs/apollo-server/api/apollo-gateway/) through the `Query._service` field (see the [Apollo Federation specification](https://www.apollographql.com/docs/apollo-server/federation/federation-spec/)) or via [`Schema#federation_sdl`](https://github.com/Gusto/apollo-federation-ruby/blob/1d3baf4f8efcd02e7bf5bc7e3fee5b4fb963cd25/lib/apollo-federation/schema.rb#L19) as explained above.## Maintainers
Gusto GraphQL Team:
- [Sara Laupp](https://github.com/slauppy)
- [Seth Copeland](https://github.com/sethc2)
- [Simon Coffin](https://github.com/simoncoffin)
- [Sofia Carrillo](https://github.com/sofie-c)