Docs
Launch Apollo Studio

Value types in Apollo Federation

Share types and fields across multiple subgraphs


⚠️ The field sharing model has changed significantly in Federation 2. For a summary of these changes, see what's new.

For more information about using value types in Federation 1, you can view the previous version of this article.

In a federated graph, it's common to want to reuse a GraphQL type in multiple subgraphs.

For example, suppose you want to define and reuse a generic Position type in different subgraphs:

type Position {
x: Int!
y: Int!
}

Types like this are called value types. This article describes how to share value types and their fields in federated graph, enabling multiple subgraphs to define and resolve them.

Sharing object types

By default in Federation 2 subgraphs, a single object field can't be defined or resolved by more than one subgraph schema.

Consider the following Position example:

Subgraph A
type Position {
x: Int!
y: Int!
}
Subgraph B
type Position {
x: Int!
y: Int!
}

Attempting to compose these two subgraph schemas together will break composition. The gateway doesn't know which subgraph is responsible for resolving Position.x and Position.y. To enable multiple subgraphs to resolve these fields, you must first mark that field as @shareable.

As an alternative, if you want Subgraphs A and B to resolve different fields of Position, you can designate the Position type as an entity.

Using @shareable

The @shareable directive enables multiple subgraphs to resolve a particular object field (or set of object fields).

To use @shareable in a subgraph schema, you first need to add the following snippet to that schema to opt in to Federation 2:

extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0",
import: ["@key", "@requires", "@shareable", "@provides", "@external"])

Then you can apply the @shareable directive to an object type, or to individual fields of that type:

Subgraph A
type Position @shareable {
x: Int!
y: Int!
}
Subgraph B
type Position {
x: Int! @shareable
y: Int! @shareable
}

Marking a type as @shareable is equivalent to marking all of its fields as @shareable, so the two subgraph definitions above are equivalent.

Both subgraphs A and B can now resolve the x and y fields for the Position type, and our subgraph schema will successfully compose into a supergraph schema.

⚠️ Important considerations for @shareable

  • If a type or field is marked @shareable in any subgraph, it must be marked @shareable or @external in every subgraph that defines it. Otherwise, composition fails.
  • If multiple subgraphs can resolve a field, make sure each subgraph's resolver for that field behaves identically. Otherwise, queries might return inconsistent results depending on which subgraph resolves the field.

Varying shared object fields

Shared fields can differ between subgraphs in specific ways:

  • The return type of a shared field can vary in nullability (String / String!).
    • A shared field's return type can't vary in its core type (Int vs. String) or in whether it returns a list (Int vs. [Int]).
  • A shared field can be omitted from a subgraph entirely if that field is always resolvable.

For example, take a look at the shared Food type below:

Subgraph A
type Food @shareable {
name: String!
cost: Int!
}
Subgraph B
type Food @shareable {
name: String!
cost: Int # Nullable
inStock: Boolean! # Not in A
}

The above Food types differ in the nullability of their fields and the fields included in each type.

Differing return types

Let's say two subgraphs both define an Event object type with a timestamp field:

Subgraph A
type Event @shareable {
timestamp: Int!
}
Subgraph B
type Event @shareable {
timestamp: String!
}

Subgraph A's timestamp returns an Int, and Subgraph B's returns a String. This is invalid. When composition attempts to generate an Event type for the supergraph schema, it fails due to an unresolvable conflict between the two timestamp field definitions.

Next, look at these varying definitions for the Position object type:

Subgraph A
type Position @shareable {
x: Int!
y: Int!
}
Subgraph B
type Position @shareable {
x: Int
y: Int
}

The x and y fields are non-nullable in Subgraph A, but they're nullable in Subgraph B. This is valid! Composition recognizes that it can use the following definition for Position in the supergraph schema:

Supergraph schema
type Position {
x: Int
y: Int
}

This definition works for querying Subgraph A, because Subgraph A's definition is more restrictive than this (a non-nullable value is always valid for a nullable field). In this case, composition coerces Subgraph A's Position fields to satisfy the reduced restrictiveness of Subgraph B.

Note that Subgraph A's actual subgraph schema is not modified. Within Subgraph A, x and y remain non-nullable.

Omitting fields

Look at these two definitions of a Position object type:

⚠️

Subgraph A
type Position @shareable {
x: Int!
y: Int!
}
Subgraph B
type Position @shareable {
x: Int!
y: Int!
z: Int!
}

Subgraph B defines a z field, but Subgraph A doesn't. In this case, when composition generates the Position type for the supergraph schema, it includes all three fields:

Supergraph schema
type Position {
x: Int!
y: Int!
z: Int!
}

This definition works for Subgraph B, but it presents a problem for Subgraph A. Let's say Subgraph A defines the following Query type:

Subgraph A
type Query {
currentPosition: Position!
}

According to the hypothetical supergraph schema, the following query is valid against the supergraph:

query GetCurrentPosition {
currentPosition {
x
y
z # ⚠️ Unresolvable! ⚠️
}
}

And here's the problem: if Subgraph B doesn't define Query.currentPosition, this query must be executed on Subgraph A. But Subgraph A is missing the Position.z field, so that field is unresolvable!

Composition recognizes this potential problem, and it fails with an error. So how do we fix it? Check out Solutions for unresolvable fields.

Enums, unions, and interfaces

In Federation 2, enum, interface, and union type definitions can be shared between subgraphs by default, and those definitions can differ:

Subgraph A
union Media = Book | Movie
enum Color {
RED
GREEN
BLUE
}
interface User {
name: String!
}
Subgraph B
union Media = Book | Podcast
enum Color {
CYAN
MAGENTA
YELLOW
}
interface User {
name: String!
age: Int!
}

Compositional logic merges these definitions in your supergraph schema:

Supergraph schema
union Media = Book | Movie | Podcast
enum Color {
RED
GREEN
BLUE
CYAN
MAGENTA
YELLOW
}
# The object types that implement this interface are
# responsible for resolving these fields.
interface User {
name: String!
age: Int!
}

This can be useful when different subgraphs are responsible for different subsets of a particular set of related types or values.

Previous
Composition
Next
Entities (basics)