Contribute and Reference Entity Fields
Contribute and reference entity fields across subgraphs
In a federated GraphQL architecture, individual subgraphs can contribute to and reference fields from shared entities. This guide explains how subgraphs work together to build a cohesive schema, with examples of contributing and computing entity fields, as well as referencing entities without contributing fields.
Contributing entity fields
Any number of different subgraphs can contribute fields to an entity definition.
In the example below, the Products and Inventory subgraphs contribute different fields to the Product
entity:
type Product @key(fields: "id") {id: ID!name: String!price: Int}
type Product @key(fields: "id") {id: ID!inStock: Boolean!}
By default, each subgraph must contribute different fields, with the important exception of @key
fields.
If multiple subgraphs attempt to contribute the same field, a composition error occurs.
To override this default behavior, see Resolving another subgraph's field.
Each subgraph that contributes fields to an entity must define a reference resolver for that entity.
Contributing computed entity fields
You can define entity fields that are computed from values of other entity fields, even when a different subgraph resolves those fields.
For example, this Shipping subgraph adds a shippingEstimate
field to the Product
entity.
This field is calculated based on the product's size
and weight
, which the Products subgraph defines:
type Product @key(fields: "id") {id: ID!size: Int @externalweight: Int @externalshippingEstimate: String @requires(fields: "size weight")}
type Product @key(fields: "id") {id: ID!name: String!price: Intsize: Intweight: Int}
Notice the Shipping subgraph uses two directives:
- The
@requires
directive indicates which fields are required from other subgraphs. - The
@external
directives is applied to required fields in the type definition.- This directive tells the router, "This subgraph knows these fields exist, but it can't resolve them itself."
How the router processes computed entity fields
In the previous example, if a query requests a product's shippingEstimate
, the router does the following:
- It queries the Products subgraph for the product's
size
andweight
. - It queries the Shipping subgraph for the product's
shippingEstimate
.
It includes the
size
andweight
of theProduct
object passed to the resolver forshippingEstimate
:resolvers.js{Product: {shippingEstimate(product) {return computeShippingEstimate(product.id, product.size, product.weight);}}}
Using @requires
with object subfields
If a computed field @requires
a field that returns an object type, you also specify which subfields of that object are required.
You list those subfields with the following syntax:
type Product @key(fields: "id") {id: ID!dimensions: ProductDimensions @externalshippingEstimate: String @requires(fields: "dimensions { size weight }")}
In this modification of the previous example, size
and weight
are now subfields of a ProductDimensions
object.
The Products and Shipping subgraphs must both define the ProductDimensions
type for this to be valid.
Starting in Federation v2.1.2, the @requires
directive can include fields that take arguments, like so:
type Product @key(fields: "id") {id: ID!weight(units: String): Int @externalshippingEstimate: String @requires(fields: "weight(units:\"KILOGRAMS\")")}
The following rules apply:
- The router provides the specified values in its query to whichever subgraph defines the required field.
- Each specified argument value is static; the router always provides the same value.
- You can omit values for nullable arguments. You must provide values for non-nullable arguments.
- If you define your subgraph schema in an SDL file instead of programmatically, you must escape quotes for string and enum values with backslashes (as shown above).
Referencing an entity without contributing fields
Your subgraphs can use an entity as a field's return type without contributing any fields to that entity.
For example, take a look at this Product
entity in the Products subgraph:
type Product @key(fields: "id") {id: ID!name: String!price: Int}
Suppose you want to create a Reviews subgraph that includes the following Review
type:
type Review {product: Product!score: Int!}
While this is possible, the current Reviews subgraph schema is invalid because it doesn't define the Product
entity.
To fix this, add a stub of the Product
entity to the Reviews schema, like so:
type Review {product: Product!score: Int!}type Product @key(fields: "id", resolvable: false) {id: ID!}
A stub definition includes only the @key
fields of an entity.
In this case, the Product
type definition only includes the id
field.
It also includes resolvable: false
in the @key
directive to indicate that this subgraph doesn't define a reference resolver for the Product
entity.