Docs
Launch GraphOS Studio

Apollo Federation subgraph specification

For adding subgraph support to GraphQL server libraries


This content is provided for developers adding federated support to a library, and for anyone curious about the inner workings of federation. You do not need to read this if you're building a with existing subgraph-compatible libraries, such as .

Servers that are partially or fully compatible with this specification are tracked in Apollo's subgraph compatibility repository.

For a service to operate as an 2 , it must do all of the following:

  • Automatically extend its schema with all definitions listed in Subgraph schema additions
  • Correctly resolve the Query._service enhanced introspection field
  • Provide a mechanism for developers to resolve via the Query._entities field

Each of these requirements is described in the sections below.

Subgraph schema additions

A must automatically add all of the following definitions to its . The purpose of each definition is described in Glossary of schema additions.

NOTE

If your library is code-first instead of schema-first (i.e., it adds schema definitions programmatically instead of via static ), use whatever API is appropriate for your library to generate these definitions on startup.

# ⚠️ This definition must be created dynamically. The union
# must include every object type in the schema that uses
# the @key directive (i.e., all federated entities).
union _Entity
scalar _Any
scalar FieldSet
scalar link__Import
scalar federation__Scope
scalar federation__Policy
enum link__Purpose {
"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY
"""
`EXECUTION` features provide metadata necessary for operation execution.
"""
EXECUTION
}
type _Service {
sdl: String!
}
extend type Query {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
}
directive @external on FIELD_DEFINITION | OBJECT
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @link(url: String!, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
directive @shareable repeatable on OBJECT | FIELD_DEFINITION
directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
directive @override(from: String!) on FIELD_DEFINITION
directive @composeDirective(name: String!) repeatable on SCHEMA
directive @interfaceObject on OBJECT
directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
directive @requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
directive @policy(policies: [[federation__Policy!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
# This definition is required only for libraries that don't support
# GraphQL's built-in `extend` keyword
directive @extends on OBJECT | INTERFACE

Enhanced introspection with Query._service

Some federated can compose their dynamically at runtime. To do so, a graph router first executes the following enhanced on each of its to obtain all :

query {
_service {
sdl
}
}

⚠️ CAUTION

Apollo strongly recommends against dynamic in the . Dynamic composition can cause unexpected downtime if composition fails on router startup. Nevertheless, supporting this use case is still a requirement for libraries.

Differences from built-in introspection

The "enhanced" above differs from the GraphQL spec's built-in introspection query in the following ways:

  • The returned schema representation is a string instead of a __Schema object.
  • The returned schema string includes all uses of federation-specific directives, such as @key.
    • The built-in 's response does not include the uses of any .
    • The requires these federation-specific to perform successfully.
  • If a "disables ", the enhanced introspection is still available.

NOTE

The _service is not included in the composed . For security reasons, it's intended solely for use by the graph .

Required resolvers for introspection

To support the enhanced , a service must define for the following :

extend type Query {
_service: _Service!
}
type _Service {
sdl: String!
}

Query._service returns a _Service object, which in turn has a single , sdl (short for ). The sdl returns a string representation of the 's schema.

The returned sdl string has the following requirements:

  • It must include all uses of all federation-specific , such as @key.
  • If supporting Federation 1, sdl must omit all automatically added definitions from Subgraph schema additions, such as Query._service and _Service.sdl!
    • If your library is only supporting Federation 2, sdl can include these defintions.

For example, consider this Federation 2 :

schema.graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3",
import: ["@key"])
type Query {
me: User
}
type User @key(fields: "id") {
id: ID!
}

The value returned for the sdl should include all of this information, including (excess whitespace can be removed).

Resolving entity fields with Query._entities

In a federated , an entity is an that can define different across multiple . You can identify an in a schema by its use of the @key .

In the following example, the Product defines its across the Products and Reviews :

Products subgraph
type Product @key(fields: "upc") {
upc: String!
name: String!
}
Reviews subgraph
type Product @key(fields: "upc") {
upc: String!
avgRating: Int!
}

If a contributes any to an , it must also provide the graph direct access to the values of those fields. To support this, a subgraph library must do the following:

  • Define the _Entity union type, which must include every type that the contributes to
  • Provide a mechanism that enables a developer to identify and return a unique instance based on its @key s
  • Define the Query._entities and resolve it using the mechanism provided to the developer

These requirements are described further in the sections below.

Defining the _Entity union

The _Entity union type is the only schema definition in Subgraph schema additions that a must generate dynamically based on the schema it's provided. All other definitions are static and can be added exactly as shown.

The _Entity union must include all types that are defined in the , except entities with a @key that sets resolvable: false.

NOTE

If a defines zero applicable types, then it should not define the _Entity union.

Example

Consider this :

Reviews subgraph
type Review @key(fields: "id") {
id: ID!
body: String
author: User
product: Product
}
type Product @key(fields: "upc") {
upc: String!
reviews: [Review!]!
}
type User @key(fields: "email", resolvable: false) {
email: String!
}

All three of the types in this are entities (note their @key ). However, the User 's @key sets resolvable: false. Therefore, the library should add the following _Entity union definition to the schema:

# Omits `User` because its @key sets resolvable: false
union _Entity = Review | Product

The _Entity union is used by the Query._entities , which is covered next.

Understanding Query._entities

If a contributes to at least one , it must automatically define and correctly resolve the Query._entities :

type Query {
_entities(representations: [_Any!]!): [_Entity]!
}

NOTE

If a doesn't define any types, then it should not define the Query._entities .

The uses this entry point to directly fetch of objects. It combines those fields with other fields of the same entity that are returned by other .

The Query._entities takes a required representations , which is a list of representations. A representation is an object that contains all from one of an entity's @keys, plus that 's __typename . These are the fields that a requires to uniquely identify a particular instance of an .

Each item in the representations list is an _Any . This is a federation-specific scalar defined in Subgraph schema additions. This is serialized as a generic JSON object, which enables the to include representations of different entities in the same , all of which can have a different shape.

Here's an example _Any representation for a Product :

{
"__typename": "Product",
"upc": "abc123"
}

The Query._entities must return a list of objects that correspond to the provided representations, in the exact same order. Entries in the list can be null if no entity exists for a provided representation.

Example

Let's say a includes two with the following schemas:

Products
type Product @key(fields: "upc") {
upc: String!
name: String!
}
type Query {
topProducts: [Product!]!
}
Reviews
type Product @key(fields: "upc") {
upc: String!
reviews: [Review!]!
}
type Review {
score: Int!
description: String!
}

With these , a client can execute the following against the graph :

query GetTopProductReviews {
topProducts {
reviews {
description
}
}
}

To resolve this , the starts by sending the following query to the Products , because that's where the top-level Query.topProducts is defined:

query {
topProducts {
__typename
upc
}
}

Notice that this includes Product.__typename and Product.upc, even though those aren't included in the original client . The knows that these two fields are used in a Product type's representation, which it will use to fetch the remaining from the Reviews .

After getting this result from the Products , the can send this followup to the Reviews subgraph:

query ($_representations: [_Any!]!) {
_entities(representations: $_representations) {
... on Product {
reviews {
description
}
}
}
}

Notice that this uses inline matching (... on Product), because the return type of Query._entities is the _Entity union type.

Each entry that the includes in the $_representations list has the following shape:

{
"__typename": "Product",
"upc": "B00005N5PF"
}

These are the representation that the obtained from its Products above.

Resolving Query._entities

As a reminder, here's the definition of the Query._entities that every must automatically define (unless a subgraph contributes fields to zero entities):

type Query {
_entities(representations: [_Any!]!): [_Entity]!
}

Every must also automatically define the for this . The logic for this resolver is as follows:

  1. Create an empty array that will contain the objects to return.
  2. For each representation included in the representations list:
    1. Obtain the 's __typename from the representation.
    2. Pass the full representation object to whatever mechanism the library provides the developer for fetching entities of the corresponding __typename.
    3. Add the fetched object to the array of entity objects. Make sure objects are listed in the same order as their corresponding representations.
  3. Return the array of objects.

Notice in step 2.2 above that the developer is responsible for defining logic that fetches a particular based on its representation. The subgraph library is responsible for providing the mechanism that developers use to specify this logic, and for automatically hooking into this mechanism in the for Query._entities.

See the next section for more details on providing this mechanism.

Providing a mechanism for fetching entities

When using your library, a developer must be able to specify logic for fetching a unique instance based on a corresponding representation of that entity. The automatically defined for Query._entities must then hook into this logic.

For example, let's look at how (with the @apollo/subgraph library) enables this via reference resolvers.

In , developers can add a special function named __resolveReference to every type that's defined in their map:

resolvers.js
// Products subgraph
const resolvers = {
Product: {
__resolveReference(productRepresentation) {
return fetchProductByUPC(productRepresentation.upc);
}
},
// ...other resolvers...
}

The Query._entities iterates through the representations it's passed and executes the corresponding __resolveReference function for each one. It passes the representation object as the first parameter to the function.

The representation object passed to the reference above might have the following structure:

{
"__typename": "Product",
"upc": "B00005N5PF"
}

For this reference , the developer calls a fetchProductByUPC function, passing the upc from the representation. This function might a database or a REST API to fetch the of Product that this knows about.

Your library does not need to use this reference pattern. It just needs to provide and some pattern for defining -fetching logic.

Glossary of schema additions

This section describes type and definitions that a valid service must automatically add to its schema. These definitions are all listed above in Subgraph schema additions.

For descriptions of added , see Federation-specific GraphQL directives.

Query fields

Query._service

This of the root Query type must return a non-nullable _Service type.

For details, see Enhanced introspection with Query._service.

Query._entities

The uses this root-level Query to directly fetch fields of entities defined by a .

This must take a representations of type [_Any!]! (a non-nullable list of non-nullable _Any scalars). Its return type must be [_Entity]! (a non-nullable list of nullable objects that belong to the _Entity union).

Each entry in the representations list must be validated with the following rules:

  • A representation must include a __typename string .
  • A representation must contain all included in the fieldset of a @key applied to the corresponding definition.

For details, see Resolving entity fields with Query._entities.

Types

type _Service

This must have an sdl: String! , which returns the of the as a string.

  • The returned schema string must include all uses of federation-specific (@key, @requires, etc.).
  • If supporting Federation 1, the schema must not include any definitions from Subgraph schema additions.

For details, see Enhanced introspection with Query._service.

union _Entity

NOTE

This union type is generated dynamically based on the input .

This union's possible types must include all entities that the defines. It's the return type of the Query._entities , which the uses to directly access a 's fields.

For details, see Defining the _Entity union.

scalar _Any

This is the type used for representations that the passes to the Query._entities . An _Any is validated by matching its __typename and @key against entities defined in the .

An _Any is serialized as a JSON object, like so:

{
"__typename": "Product",
"upc": "abc123"
}

scalar FieldSet

This string-serialized represents a set of that's passed to a federated , such as @key, @requires, or @provides.

Grammatically, a FieldSet is a selection set minus the outermost curly braces. It can represent a single ("upc"), multiple ("id countryCode"), and even nested selection sets ("id organization { id }").

scalar Scope

This string-serialized represents a JWT scope.

scalar Policy

This string-serialized represents an authorization policy.

Directives

See Federation-specific GraphQL directives.

Previous
OpenTelemetry
Next
Changes from Federation 1
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company