Overview
It's time to witness the true strength of federation; we're about to join the pieces and make our queries more powerful and dynamic than ever before.
In this lesson, we will:
- Learn what an entity type is, what it's used for, and how to define it
- Convert the
Listingtype into an entity
Reviews and listings
Remember our dream query?
query GetListingAndReviews {listing(id: "listing-1") {titledescriptionnumOfBedsamenities {namecategory}overallRatingreviews {idtext}}}
As this query shows, we want reviews and overallRating to be fields on the Listing type.
The big problem with that? As we've previously discussed, the reviews subgraph has no idea about the types in the listings subgraph—much less that there's a Listing type it should be adding a field to! Let's fix that—with entities.
What's an entity?
An entity is an object type with fields split between multiple subgraphs .It's the fundamental building block of a federated graph architecture, used to connect data between subgraphs while still adhering to the separation of concerns principle.
When working with an entity, each subgraph can do one or both of the following:
- Contribute different fields to the entity
- Reference an entity, which means using it as a return type for another field defined in the subgraph
Contributing vs. referencing
To differentiate between subgraphs that contribute to an entity, and those that reference an entity, think of it this way: a subgraph that contributes fields is actually adding new data capabilities from its own domain to the entity type.
This is in contrast to a subgraph that merely references the entity; it's essentially just "mentioning" the existence of the entity, and providing it as the return type for another field.
In federation, we use entities to create cohesive types that aren't confined to just one subgraph or another; instead, they can span our entire API!
In our example, we want to supplement our Listing type with reviews-related data. That makes the Listing type a perfect candidate to become an entity; it needs to have fields that are defined in both of our subgraphs. This lets us build a more powerful representation of what a "listing" is in our API, and the different data that makes it up.
To create an entity, a subgraph needs to provide two things: a primary key and a reference resolver.
Defining a primary key
An entity's primary key is the field (or fields) that can uniquely identify an instance of that entity within a subgraph. Just like in a database, this is the information that lets us tell one thing from another. When we talk about listings, we use their id field to identify which listing we're talking about.
The router uses primary keys to collect data from across multiple subgraphs and associate it with a single entity instance. It's how we know that each subgraph is talking about—and providing data for—the same object!

We use the @key directive, along with a property called fields to set the field we want to use as the entity's primary key.
type EntityType @key(fields: "id") {# ...}
Reference resolvers and entity representations
Because an entity's fields can be divided across subgraphs, each subgraph that provides fields needs a way to identify the instance the router is gathering data for. When it's able to identify this instance, the subgraph can provide additional data. Identifying a particular instance of an entity takes the form of a method called a reference resolver.
This method receives a basic object called an entity representation. An entity representation is an object that the router uses to identify a specific instance of an entity. A representation includes the typename for that entity and the @key field for the specific instance.
The
__typenamefield: This field exists on all GraphQL types automatically. It always returns the name of its containing type, as a string. For example,Listing.__typenamereturns "Listing".The
@keyfield: The key-value pair that a subgraph can use to identify the instance of an entity. For example, if we defined theListingentity using the "id" field as a primary key, then our entity representation would include an "id" property with a value like "listing-3".
An entity representation passed to each subgraph resolving fields for a particular Listing might look like this:
{"__typename": "Listing","id": "listing-3"}
You can think of the entity representation as the minimum basic information the router needs to associate data from multiple subgraphs, and ensure that each subgraph is talking about the same object.
As we'll see shortly, the entity representation for a particular listing will give our reviews subgraph all the information it needs (nothing more, nothing less!) to contribute the relevant reviews data.
Creating the Listing entity
Bringing this all back to our dream query! We want to make the Listing type an entity so that the reviews subgraph can contribute two fields to it: overallRating and reviews.
Let's get to it!
In the listings subgraph
With your rover dev process still running, open up the listings subgraph and navigate to schema.graphqls. Scroll down to find the Listing type.
Here we'll apply the @key directive, passing in a fields property set to "id".
type Listing @key(fields: "id") {id: ID!# ... other Listing fields}
When we save our changes, rover dev will automatically pick up on the new schema and restart the composition process. Make sure it composes successfully before moving on to the reviews subgraph.
In the reviews subgraph
Open up your code editor to the reviews subgraph, and navigate to the src/main/resources/schema/schema.graphqls file. We'll start by adding a basic stub of the Listing type: this consists of the typename, the @key directive, and the field assigned as its primary key, id.
type Listing @key(fields: "id") {id: ID!}
Notice that we don't include any other fields from the Listing entity as defined in the listings subgraph. We can keep our reviews subgraph clean and concerned only with the data it has the responsibility of providing.
Let's add that reviews field to the Listing type. We'll give it a return type of [Review!]!. We'll also define the overallRating, which returns a nullable Float type.
type Listing @key(fields: "id") {id: ID!"The submitted reviews for this listing"reviews: [Review!]!"The overall calculated rating for a listing"overallRating: Float}
Testing our schema changes
Our rover dev process should still be running, so let's jump back to http://localhost:4000.
Now we'll see that the reviews field is available under the Listing type. Our small schema changes have made it possible to construct our dream query!
query GetListingAndReviews {listing(id: "listing-1") {titledescriptionnumOfBedsamenities {namecategory}overallRatingreviews {idtext}}}
Though it's lovely to look at, our query can't actually return data yet. While the listings subgraph can easily resolve its listing fields (id, title, description, numOfBeds, and amenities), we still haven't given the reviews subgraph any idea of what to do when the router requests a particular listing's reviews or overallRating.
In fact, if we run the query, we'll see just a bunch of gnarly errors.
Practice
Drag items from this box to the blanks above
fieldsmore than one
router
@uniquereference resolver
only one
@primarysubgraph
keys
@key
Key takeaways
- An entity is a type that can resolve its fields across multiple subgraphs.
- To create an entity, we can use the
@keydirective to specify which field(s) can uniquely identify an object of that type. - We can use entities in two ways:
- As a return type for a field (referencing an entity).
- Defining fields for an entity from multiple subgraphs (contributing to an entity).
Up next
In the next lesson, we'll equip our reviews subgraph with logic to actually return data.
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.