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

, such as .

Servers that are partially or fully compatible with this specification are tracked in Apollo's

.

For a service to operate as an 2 subgraph, 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 subgraph developers to resolve via the
    Query._entities field

Each of these requirements is described in the sections below.

Subgraph schema additions

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

.

NOTE

If your GraphQL server 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 graph . Dynamic composition can cause unexpected downtime if composition fails on router startup. Nevertheless, supporting this use case is still a requirement for subgraph libraries.

Differences from built-in introspection

The "enhanced" introspection query above differs from

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 introspection query's response does not include the uses of any .
    • The graph router requires these federation-specific directives to perform composition successfully.
  • If a "disables introspection", the enhanced introspection query is still available.

NOTE

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

Required resolvers for introspection

To support the enhanced introspection query, a subgraph service must define for the following fields:

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

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

The returned sdl string has the following requirements:

  • It must include all uses of all federation-specific directives, 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 field should include all of this information, including directives (excess whitespace can be removed).

Resolving entity fields with Query._entities

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

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

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

If a subgraph contributes any fields to an entity, it must also provide the graph router 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 entity type that the subgraph contributes fields to
  • Provide a mechanism that enables a subgraph developer to identify and return a unique entity instance based on its @key fields
  • Define the Query._entities field and resolve it using the mechanism provided to the subgraph developer

These requirements are described further in the sections below.

Defining the _Entity union

The _Entity union type is the only schema definition in

that a subgraph 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 entity types that are defined in the subgraph schema, except entities with a @key that sets resolvable: false.

NOTE

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

Example

Consider this subgraph schema:

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 subgraph schema are entities (note their @key directives). However, the User entity's @key sets resolvable: false. Therefore, the subgraph 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 field, which is covered next.

Understanding Query._entities

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

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

NOTE

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

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

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

Each item in the representations list is an _Any . This is a federation-specific scalar defined in

. This scalar is serialized as a generic JSON object, which enables the graph router to include representations of different entities in the same query, all of which can have a different shape.

Here's an example _Any representation for a Product entity:

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

The Query._entities field must return a list of entity 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 supergraph includes two subgraphs 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 subgraph schemas, a client can execute the following query against the graph router:

query GetTopProductReviews {
topProducts {
reviews {
description
}
}
}

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

query {
topProducts {
__typename
upc
}
}

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

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

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

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

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

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

These are the representation fields that the router obtained from its Products query above.

Resolving Query._entities

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

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

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

  1. Create an empty array that will contain the entity objects to return.
  2. For each entity representation included in the representations list:
    1. Obtain the entity's __typename from the representation.
    2. Pass the full representation object to whatever mechanism the library provides the subgraph developer for fetching entities of the corresponding __typename.
    3. Add the fetched entity 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 entity objects.

Notice in step 2.2 above that the subgraph developer is responsible for defining logic that fetches a particular entity 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 resolver for Query._entities.

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

Providing a mechanism for fetching entities

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

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

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

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

The Query._entities resolver 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 resolver above might have the following structure:

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

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

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

Glossary of schema additions

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

.

For descriptions of added directives, see

.

Query fields

Query._service

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

.

For details, see

.

Query._entities

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

This field must take a representations argument of type [_Any!]! (a non-nullable list of non-nullable

). 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 field.
  • A representation must contain all fields included in the fieldset of a @key directive applied to the corresponding entity definition.

For details, see

.

Types

type _Service

This object type must have an sdl: String! field, which returns the SDL of the subgraph schema as a string.

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

For details, see

.

union _Entity

NOTE

This union type is generated dynamically based on the input subgraph schema.

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

For details, see

.

scalar _Any

This scalar is the type used for entity representations that the graph router passes to the Query._entities field. An _Any scalar is validated by matching its __typename and @key fields against entities defined in the subgraph schema.

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

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

scalar FieldSet

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

Grammatically, a FieldSet is a

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

scalar Scope

This string-serialized scalar represents a JWT scope.

scalar Policy

This string-serialized scalar represents an authorization policy.

Directives

See

.

Previous
OpenTelemetry
Next
Changes from Federation 1
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company