OpenFederation Specification
Introduction
OpenFederation is a specification for building federated GraphQL APIs.
What is currently known as Apollo Federation
is a set of GraphQL directives that, if implemented by a GraphQL server, enables the server to participate in a federated GraphQL API. It’s also known as the Subgraph Specification.
What was missing in the GraphQL ecosystem was a clear definition of how Schema Composition in federated GraphQL APIs should be performed, and how the resulting federated GraphQL Schema should be executed by a Federated Gateway or Router.
The OpenFederation Specification is an attempt to provide a single document that covers all of the above. The goal is to enable a diverse ecosystem of Federated GraphQL implementations that are compatible with each other.
Defining such a core specification is a community effort, and we welcome your contributions. We believe that a diverse ecosystem of implementations with healthy competition will benefit the GraphQL community as a whole.
Status
This specification is currently in draft form and in early stage. Once the specification is more mature, we will make it publicly available and invite the community to contribute.
1Terminology
The Terminology section will define a common set of terms that will be used throughout the specification, like Federated Graph
, Subgraph
, Federated Schema
, Router
, etc.
1.1Entity
Entities are object (extension) types (and in Federation V2.3+, interface types) that define one or more identifiable references (primary keys) through @key directives. These identifiable reference allows different subgraphs to contribute and resolve fields for the respective entity.
1.2Federated Graph
Sometimes referred to as “supergraph” or “composed graph”, a federated graph is the resulting GraphQL schema when one or more subgraph (service) schemas are successfully federated (composed into a single schema). A federated graph allows you to make all requests (queries, mutations, and subscriptions) through the federated graph rather than separately through the constituent subgraphs. It is the router’s job to “route” the different parts of these requests to the correct subgraphs so that the requested data is fully resolved.
1.3Requirement
Requirement pertains to the “nullability” of something such as a response or argument. For instance, a required argument cannot be omitted or null; a required response cannot return null.
“Required” is sometimes referred to as “non-nullable” or “non-optional”. A required type definition is represented by a trailing exclamation mark (!
) in GraphQL.
“Optional” is sometimes referred to as “nullable” or “un-/non-required”. An optional type definition is represented by the lack of trailing exclamation mark in GraphQL.
In example 1, below, the field “names” must never return null. However, the list type that should always be returned may itself return null entries.
# Example 1
type User {
names: [String]!
}
In example 2, below, the field “names” may return null. However, if the response itself is not null, the list type must not return any null entries.
# Example 2
type User {
names: [String!]
}
1.4Subgraph
Sometimes referred to as “microservice”, “service”, or even simply “graph”, a subgraph is a GraphQL API. A federated graph is composed of one or more subgraphs.
2Subgraph
The Subgraph section will re-define the Subgraph Specification. It will explain the different directives that are used to define a Subgraph, and how they can solve the different use cases.
3Federated Graph
The section Federated Graph will define the core concepts of a Federated Graph.
4Validation
The Validation section will define in-depth the validation rules for implementing Subgraph composition checks.
5Composition
The Composition section will define the composition algorithm that should be used to generate a Federated Schema from a set of Subgraph Schemas. Please note that minimal examples are used to illustrate specific points. The examples are not intended to be considered full, composable schemas.
5.1Field Arguments
In this section, “field argument” refers to an argument of a specific name defined on a field that is defined in multiple subgraphs.
Field arguments are composable if the following criteria are met:
- Each type definition is a superset of the most restrictive type definition.
- Any defined default values are identical (they may also be omitted).
If any of the criteria listed above are unmet, it is a compositional error.
5.1.1Type Definitions
Field arguments must be defined in all instances of the field to be present in the federated graph. The most restrictive type definition will be the type definition that appears in the federated graph.
- Example FA–TD1:
# subgraph 1
type Object {
field(arg: [Int!]): Int
}
# subgraph 2
type Object {
field(arg: [Int!]!): Int
}
# federated subgraph
type Object {
field(arg: [Int!]!): Int
}
If an optional field argument is undefined in one or more subgraphs, it will not appear in the federated graph.
- Example FA–TD2:
# subgraph 1
type Object {
field(arg: [Int!]): Int
}
# subgraph 2
type Object {
field: Int
}
# federated subgraph
type Object {
field: Int
}
Conversely, if an optional field argument is defined in all subgraphs, the most restrictive type definition will be present in the federated graph.
- Example FA–TD3:
# subgraph 1
type Object {
field(arg: [Int!]): Int
}
# subgraph 2
type Object {
field(arg: [Int]): Int
}
# federated subgraph
type Object {
field(arg: [Int!]): Int
}
If the type definition of a field argument is not a superset of the most restrictive type definition, it is a compositional error. Of particular note, if a field argument is required in any subgraph, it must not be undefined in any other subgraphs.
- Counter Example FA–TD4 (
null
is not a superset ofInt!
):
# subgraph 1
type Object {
field(arg: Int!): Int
}
# subgraph 2
type Object {
field: Int
}
# The result is a compositional error
Some further counter examples are provided below.
- Counter Example FA–TD5 (
Float
is not a superset ofInt
):
# subgraph 1
type Object {
field(arg: Int): Int
}
# subgraph 2
type Object {
field(arg: Float): Int
}
# The result is a compositional error
- Counter Example FA–TD6 (
[Int]
is not a superset ofInt
):
# subgraph 1
type Object {
field(arg: Int): Int
}
# subgraph 2
type Object {
field(arg: [Int]): Int
}
# The result is a compositional error
- Counter Example FA–TD7 (
[[Int!]]!
is not a superset of[[Int]!]!
):
# subgraph 1
type Object {
field(arg: [[Int]!]!): Int
}
# subgraph 2
type Object {
field(arg: [[Int!]]!): Int
}
# The result is a compositional error
5.1.2Default Values
Default values must be identically defined in all instances of the field argument to be present in or apply to the federated graph.
If a default value is omitted in one or more subgraphs, but any other default value definitions are identical, the default value will not be present or applied to the federated graph.
- Example FA–DV1:
# subgraph 1
type Object {
field(arg: Int = 1): Int
}
# subgraph 2
type Object {
field(arg: Int): Int
}
# subgraph 3
type Object {
field(arg: Int = 1): Int
}
# federated graph
type Object {
field(arg: Int): String
}
Among the default values that are defined, if any are non-identical, it is a compositional error.
- Counter Example FA–DV2:
# subgraph 1
type Object {
field(arg: Int = 1): Int
}
# subgraph 2
type Object {
field(arg: Int = 2): Int
}
# subgraph 3
type Object {
field(arg: Int = 1): Int
}
# The result is a compositional error
6Execution
The Execution section will define the core execution algorithms that need to be implemented by a Router to execute Federated GraphQL Operations.