Entities in Apollo Federation
Reference and extend types across subgraphs
In Apollo Federation, an entity is an object type that you define canonically in one subgraph and can then reference and extend in other subgraphs.
Entities are the core building block of a federated graph.
Defining entities
In a GraphQL schema, you can designate any object type as an entity by adding a @key
directive to its definition, like so:
1type Product @key(fields: "upc") {
2 upc: String!
3 name: String!
4 price: Int
5}
Types besides object types (such as unions and interfaces) cannot be entities.
The @key
directive defines the entity's primary key, which consists of one or more of the type's fields
. Like primary keys in other systems, an entity's primary key must uniquely identify a particular instance of that entity.
In the example above, the Product
entity's primary key is its upc
field. The gateway uses an entity's primary key to match data from different subgraphs to the same object instance.
An entity's
@key
cannot include fields that return a union or interface.
Multiple primary keys
You can define more than one primary key for an entity, when applicable.
In the following example, a Product
entity can be uniquely identified by either its upc
or its sku
:
1type Product @key(fields: "upc") @key(fields: "sku") {
2 upc: String!
3 sku: String!
4 price: String
5}
This pattern is helpful when different subgraphs interact with different fields of an entity. For example, a reviews
subgraph might refer to products by their UPC, whereas an inventory
subgraph might use SKUs.
Compound primary keys
A single primary key can consist of multiple fields, and even nested fields.
The following example shows a primary key that consists of both a user's id
and the id
of that user's associated organization:
1type User @key(fields: "id organization { id }") {
2 id: ID!
3 organization: Organization!
4}
5
6type Organization {
7 id: ID!
8}
Referencing entities
After you define an entity in one subgraph, other subgraphs can then reference that entity in their schema.
For example, let's say we have a products
subgraph that defines the following Product
entity:
1type Product @key(fields: "upc") {
2 upc: String!
3 name: String!
4 price: Int
5}
A reviews
subgraph can then add a field of type Product
to its Review
type, like so:
1type Review {
2 score: Int!
3 product: Product!
4}
5
6# This is a required "stub" of the Product entity (see below)
7extend type Product @key(fields: "upc") {
8 upc: String! @external
9}
To reference an entity that originates in another subgraph, the reviews
subgraph needs to define a stub of that entity to make its own schema valid. The stub includes just enough information for the subgraph to know how to uniquely identify a particular Product
:
The
extend
keyword indicates thatProduct
is an entity that's defined in another subgraph.The
@key
directive indicates thatProduct
uses theupc
field as its primary key. This value must match the value of exactly one@key
defined in the entity's originating subgraph (even if the entity defines multiple primary keys).The
upc
field must be present because it's part of the specified@key
. It also requires the@external
directive to indicate that it originates in another subgraph.
This explicit syntax has several benefits:
It's standard GraphQL grammar.
It enables you to run the
reviews
subgraph standalone with a valid schema, including aProduct
type with a singleupc
field.It provides strong typing information that lets you catch mistakes at schema composition time.
Resolving entities
Let's say our reviews
subgraph from Referencing entities defines the following Query
type:
1type Query {
2 latestReviews: [Review!]
3}
That means the following query is valid against our federated graph:
1query GetReviewsAndProducts {
2 latestReviews {
3 score
4 product {
5 upc
6 price # Not defined in reviews!
7 }
8 }
9}
Now we have a problem: this query starts its execution in the reviews
subgraph (where latestReviews
is defined), but that subgraph doesn't know that Product
entities have a price
field! Remember, the reviews
subgraph only knows about its stub fields of Product
.
Because of this, the gateway needs to fetch price
from the products
subgraph instead. But how does the gateway know which products it needs to fetch the prices for?
To solve this, we add a resolver to each subgraph:
A resolver in
reviews
to generate representations ofProduct
entitiesA reference resolver in
products
to return fullProduct
objects from representations
Entity representations
In our example, the reviews
subgraph needs to define a resolver for its stub version of the Product
entity. The reviews
subgraph doesn't know much about Product
s, but fortunately, it doesn't need to. All it needs to do is return data for the fields it does know about, like so:
1// Reviews subgraph
2const resolvers = {
3 Review: {
4 product(review) {
5 return {
6 __typename: "Product",
7 upc: review.upc
8 };
9 }
10 },
11 // ...
12}
This resolver's return value is a representation of a Product
entity (because it represents an entity from another subgraph). A representation always consists of:
A
__typename
fieldValues for the entity's primary key fields (
upc
in this example)
Because an entity can be uniquely identified by its primary key fields, this is all the information the gateway needs to fetch additional fields for a Product
object.
Reference resolvers
As a reminder, here's the example query we're executing across our subgraphs:
1query GetReviewsAndProducts {
2 latestReviews {
3 score
4 product {
5 upc
6 price # Not defined in reviews!
7 }
8 }
9}
The gateway knows it can't fetch Product.price
from the reviews
subgraph, so first it executes the following query on reviews
:
1