Entities in Apollo Federation
Resolve types across multiple subgraphs
⚠️ Some details of entity behavior have changed in Federation 2. For a summary of these changes, see what's new.
In a federated graph, an entity is an object type that can resolve its fields across multiple subgraphs. Each subgraph can contribute different fields to the entity and is responsible for resolving only the fields that it contributes.
For example, this Product
entity's fields are defined and resolved across two subgraphs:
type Product @key(fields: "id") {id: ID!name: String!price: Int}
type Product @key(fields: "id") {id: ID!inStock: Boolean!}
Entities are a fundamental building block of Apollo Federation that enable subgraphs to adhere to the separation of concerns principle.
Types besides object types (unions, interfaces, etc.) cannot be entities.
Defining an entity
To fully define an entity within a single subgraph, you do the following:
- Assign the entity a
@key
- Define the entity's reference resolver
If a subgraph defines an existing entity but doesn't resolve any fields for it, these steps aren't necessary. See Referencing an entity without contributing fields.
1. Define a @key
In a subgraph schema, you can designate any existing object type as an entity by adding the @key
directive to its definition, like so:
type Product @key(fields: "id") {id: ID!name: String!price: Int}
The @key
directive defines the entity's primary key, which consists of one or more of the type's fields
. In this example, the Product
entity's primary key is its id
field.
Every instance of an entity must be uniquely identifiable by its @key
fields. This is what enables your gateway to associate field data from different subgraphs with the same entity instance.
An entity's @key
cannot include:
- Fields that return a union or interface
- Fields that take arguments
2. Define a reference resolver
The @key
directive effectively tells the gateway, "This subgraph can resolve an instance of this entity if you provide its primary key." In order for this to be true, the subgraph needs to define a reference resolver for the entity.
⚠️ This section describes how to create reference resolvers in Apollo Server. If you're using another subgraph-compatible library, see its documentation for creating reference resolvers.
For the Product
entity defined above, the reference resolver might look like this:
// Products subgraphconst resolvers = {Product: {__resolveReference(productRepresentation) {return fetchProductByID(productRepresentation.id);}},// ...other resolvers...}
Let's break this example down:
- You declare an entity's reference resolver in your resolver map, as a member of the entity's corresponding object.
- A reference resolver's name is always
__resolveReference
. - A reference resolver's first parameter is a representation of the entity being resolved.
- An entity representation is an object that contains the entity's
@key
fields, plus its__typename
field. These values are provided by the gateway.
- An entity representation is an object that contains the entity's
- A reference resolver is responsible for returning all of the entity fields that this subgraph defines.
- In this example, the hypothetical
fetchProductByID
function fetches a particularProduct
's field data based on itsid
.
- In this example, the hypothetical
Every subgraph that contributes at least one unique field to an entity must define a reference resolver for that entity.
To learn more about __resolveReference
in Apollo Server, see the API docs.
Contributing entity fields
Any number of different subgraphs can contribute fields to an entity definition. 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!}
When a subgraph contributes entity fields, no other subgraph knows about those fields—only the gateway does thanks to the composed supergraph schema.
By default, each subgraph must contribute different fields, with the important exception of @key
fields. Otherwise, a composition error occurs. To override this default, see Resolving another subgraph's field.
As mentioned previously, each subgraph that does contribute fields to an entity must define a reference resolver for that entity.
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. This requires less code than the steps in Defining an entity.
Take a look at this Product
entity in the Products subgraph:
type Product @key(fields: "id") {id: ID!name: String!price: Int}
Now, let's say we want to create a Reviews subgraph that includes the following Review
type:
type Review {product: Product!score: Int!}
This is possible! However, this subgraph schema is currently invalid because it doesn't define the Product
entity.
To fix this, we can 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!}
As you can see, this stub definition includes only the @key
fields of Product
(just id
in this case). It also includes resolvable: false
in the @key
directive to indicate that this subgraph doesn't even define a reference resolver for the Product
entity.
Example query flow
To help understand how entities are resolved across subgraphs, let's look at an example query executed on an example federated graph.
Let's say we have these two subgraphs that both define the Product
entity:
type Product @key(fields: "id") {id: ID!name: String!price: Int}
type Product @key(fields: "id", resolvable: false) {id: ID!}type Review {score: Int!description: String!product: Product!}type Query {latestReviews: [Review!]!}
Notice that the Reviews subgraph references the Product
entity without contributing fields.
The Reviews subgraph defines one entry point into our schema: Query.latestReviews
. This means that the following query is valid against our gateway:
query GetReviewsWithProducts {latestReviews { # Defined in Reviewsscoreproduct {idprice # ⚠️ NOT defined in Reviews!}}}
Here we have a problem: this query needs to start its execution in the Reviews subgraph (because that's where latestReviews
is defined), but that subgraph doesn't know that Product
entities have a price
field! Remember, the Reviews subgraph only knows about the id
field of Product
.
Because of this, the gateway needs to fetch price
from the Products subgraph instead. To handle this two-step process, the gateway generates a query plan.
The query plan
Query plans are automatically generated and carried out by the gateway. You don't need to write any code related to them.
A query plan is a blueprint for dividing a single incoming operation into one or more operations that are each resolvable by a single subgraph. The gateway generates a query plan for each unique operation that it receives from clients.
With our example query above, the gateway knows the following:
- It must start by querying the Reviews subgraph, because that's where
Query.latestReviews
is defined. - It must then query the Products subgraph to fetch the
price
of eachProduct
returned by the Reviews subgraph.
Using this information, the gateway's query plan starts with this query to the Reviews subgraph:
query {latestReviews {scoreproduct {__typenameid}}}
Notice that this query omits the Product.price
field but adds the Product.__typename
field! This is because the gateway needs representations of each returned Product
entity for its second query.
As described in Define a reference resolver, an entity representation is an object that contains the entity's @key
fields (id
in this case), plus its __typename
field.
This first query returns a list of Review
objects, each containing a Product
representation. With these representations, the gateway can execute its second query, this time on the Products subgraph:
query {_entities(representations: [...]) {... on Product {price}}}
This query uses a special entry point that's automatically added to every subgraph schema: Query._entities
. This entry point is what provides the gateway with random access to any entity's fields.
Each item in the representations
list argument above is one of the Product
representations that the gateway obtained from its first query. Here's an example list:
[{"__typename": "Product","id": "1"},{"__typename": "Product","id": "2"},//...]
These representations are passed individually to the Product
reference resolver in the Products subgraph. When the reference resolver finishes returning values for each representation, the gateway receives its response:
[{"price": 100},{"price": 200},//...]
Nice! The gateway can merge this price
data with the Product
objects returned by its first query. After doing so, the gateway returns a single, combined result to the client.