Docs
Launch GraphOS Studio

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 , an entity is an that can resolve its across multiple . Each subgraph can contribute different fields to the and is responsible for resolving only the fields that it contributes.

For example, this Product 's are defined and resolved across two :

Products subgraph
type Product @key(fields: "id") {
id: ID!
name: String!
price: Int
}
Inventory subgraph
type Product @key(fields: "id") {
id: ID!
inStock: Boolean!
}

Entities are a fundamental building block of that enable to adhere to the separation of concerns principle.

Entities are always object types (never input types, unions, and so on).

Defining an entity

To define an within a particular , you do the following:

  1. Apply the @key directive to an .
  2. Define the 's reference resolver.

These steps are described below.

1. Define a @key

In a , you can designate any existing as an by adding the @key to its definition, like so:

Products subgraph
type Product @key(fields: "id") {
id: ID!
name: String!
price: Int
}

The @key defines an 's unique key, which consists of one or more of the type's fields. In the example above, the Product 's unique key is its id . Every instance of an entity must be uniquely identifiable by its @key fields. This is what enables your to associate data from different with the same instance.

In most cases, the @key (s) for the same will be the same across . For example, if one subgraph uses id as the @key for the Product , other should do the same. However, this isn't strictly required.

If coming from a database context, it can be helpful to think of a @key as an 's primary key. This term isn't completely accurate for entities since a single can have multiple @keys. The (s) you select for an 's @key must, however, uniquely identify the . In that way, @keys are similar to candidate keys.

Products subgraph
type Product @key(fields: "id") {
id: ID!
name: String!
price: Int
}
Inventory subgraph
type Product @key(fields: "id") {
id: ID!
inStock: Boolean!
}

An entity's @key cannot include:

  • that return a union or interface
  • that take s

Though not strictly required, it's best to use non-nullable for keys. If you use fields that return null values, may encounter issues resolving the .

For more information on advanced key options, like how to define multiple keys or compound keys, see Advanced topics for federation entities.

2. Define a reference resolver

The @key effectively tells the , "This can resolve an instance of this if you provide its unique key." In order for this to be true, the subgraph needs to define a reference resolver for the .

⚠️ 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 (or the equivalent functionality).

For the Product defined above, the reference might look like this:

resolvers.js
// Products subgraph
const resolvers = {
Product: {
__resolveReference(productRepresentation) {
return fetchProductByID(productRepresentation.id);
}
},
// ...other resolvers...
}

Let's break this example down:

  • You declare an 's reference in your resolver map, as a member of the entity's corresponding object.
  • A reference 's name is always __resolveReference.
  • A reference 's first parameter is a representation of the being resolved.
    • An representation is an object that contains the entity's @key , plus its __typename . These values are automatically provided to your by your .
  • A reference is responsible for returning all of the that this defines.
    • In this example, the hypothetical fetchProductByID function fetches a particular Product's data based on its id.

⚠️ A particular reference resolver might be called many times to resolve a single query! It's crucial that reference account for "N+1" issues (typically via data loaders). For details, see Handling the N+1 problem.

Every that contributes at least one unique to an must define a reference for that .

To learn more about __resolveReference in , see the API docs.

Contributing entity fields

Any number of different can contribute to an definition. Below, the Products and Inventory subgraphs contribute different fields to the Product :

Products subgraph
type Product @key(fields: "id") {
id: ID!
name: String!
price: Int
}
Inventory subgraph
type Product @key(fields: "id") {
id: ID!
inStock: Boolean!
}

When a contributes , no other knows about those —only the does thanks to the composed .

By default, each must contribute different , with the important exception of @key . Otherwise, a error occurs. To override this default, see Resolving another subgraph's field.

As mentioned previously, each that does contribute to an must define a reference resolver for that .

Referencing an entity without contributing fields

Your can use an as a 's return type without contributing any to that . This requires less code than the steps in Defining an entity.

Take a look at this Product in the Products :

Products subgraph
type Product @key(fields: "id") {
id: ID!
name: String!
price: Int
}

Now, let's say we want to create a Reviews that includes the following Review type:

Reviews subgraph
type Review {
product: Product!
score: Int!
}

This is possible! However, this is currently invalid because it doesn't define the Product .

To fix this, we can add a stub of the Product to the Reviews schema, like so:

Reviews subgraph
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 of Product (just id in this case). It also includes resolvable: false in the @key to indicate that this doesn't even define a reference for the Product .

Example query flow

To help understand how entities are resolved across , let's look at an example executed on an example .

Let's say we have these two that both define the Product :

Products subgraph
type Product @key(fields: "id") {
id: ID!
name: String!
price: Int
}
Reviews subgraph
type Product @key(fields: "id", resolvable: false) {
id: ID!
}
type Review {
score: Int!
description: String!
product: Product!
}
type Query {
latestReviews: [Review!]!
}

The Reviews defines one entry point into our schema: Query.latestReviews. This means that the following is valid against our :

query GetReviewsWithProducts {
latestReviews { # Defined in Reviews
score
product {
id
price # ⚠️ NOT defined in Reviews!
}
}
}

Here we have a problem: this needs to start its execution in the Reviews (because that's where latestReviews is defined), but that doesn't know that Product entities have a price ! Remember, the Reviews only knows about the id of Product.

Because of this, the needs to fetch price from the Products instead. To handle this two-step process, the generates a query plan.

The query plan

are automatically generated and carried out by your . You don't need to write any code related to them.

A is a blueprint for dividing a single incoming into one or more operations that are each resolvable by a single . Your generates a query plan for each unique operation that it receives from clients.

With our example above, the knows the following:

  • It must start by the Reviews , because that's where Query.latestReviews is defined.
  • It must then the Products to fetch the price of each Product returned by the Reviews .

Using this information, the 's starts with this query to the Reviews :

query {
latestReviews {
score
product {
__typename
id
}
}
}

Notice that this omits the Product.price but adds the Product.__typename ! This is because the needs representations of each returned Product for its second .

As described in Define a reference resolver, an representation is an object that contains the entity's @key (id in this case), plus its __typename .

This first returns a list of Review objects, each containing a Product representation. With these representations, the can execute its second , this time on the Products :

query {
_entities(representations: [...]) {
... on Product {
price
}
}
}

This uses a special entry point that's automatically added to every : Query._entities. This entry point is what provides the with direct access to any 's .

Each item in the representations list above is one of the Product representations that the obtained from its first . 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 . When the reference finishes returning values for each representation, the receives its response:

[
{
"price": 100
},
{
"price": 200
},
//...
]

Nice! The can merge this price data with the Product objects returned by its first . After doing so, the returns a single, combined result to the client.

Advanced topics

See Advanced topics on federated entities.

Previous
Sharing types (value types)
Next
Entities (advanced)
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company