Overview
In the previous lesson, we just saw how subgraphs can reference an entity as a field's return type. Now, let's take a look at how subgraphs can contribute fields to an entity.
In this lesson, we will:
- Learn how multiple subgraphs can contribute fields to an entity
- Update the
Location
entity in ourreviews
subgraph schema by contributing thereviewsForLocation
andoverallRating
fields
✏️ Contributing fields
Remembering our FlyBy UI, we know it needs to fetch each location's overallRating
, along with a list of its reviewsForLocation
:
By following the separation of concerns principle, it makes sense that any data about ratings or reviews is populated by the reviews
subgraph, so let's head on over there and make those additions!
Open up the
subgraph-reviews/reviews.graphql
file.Find the
Location
entity definition in the schema. We previously defined this as a stub of theLocation
type.subgraph-reviews/reviews.graphqltype Location @key(fields: "id", resolvable: false) {id: ID!}By default, a subgraph should only contribute fields that aren't defined by other subgraphs, with the exception of the primary key field. This means that because the
locations
subgraph definesname
,description
, andphoto
as fields for theLocation
type, we won't - and shouldn't - define those fields here in thereviews
subgraph!Note: You can override the default behavior explained above to allow multiple subgraphs to resolve the same field by applying either the
@shareable
or@provides
directive. This is an optional performance optimization that can instruct the router on how to plan the execution of a query across as few subgraphs as possible.Because we now want the
reviews
subgraph to contribute new fields to theLocation
definition, the first thing we need to do is remove theresolvable: false
property from the@key
directive. This will enable ourreviews
subgraph to define and resolve its ownLocation
fields.Remove the
resolvable: false
property from theLocation
type's@key
directive.subgraph-reviews/reviews.graphqltype Location @key(fields: "id") {id: ID!}
✏️ Adding new fields to the Location entity
Now we're ready to add the two new fields.
- the
overallRating
field, which returns aFloat
- the
reviewsForLocation
field, which returns a non-null list ofReview
objects.
We'll also add descriptions to these fields so we can quickly see what they represent.
type Location @key(fields: "id") {id: ID!"The calculated overall rating based on all reviews"overallRating: Float"All submitted reviews about this location"reviewsForLocation: [Review]!}
✏️ Adding resolvers
Each of these fields needs a resolver function to return data, so let's take care of that next.
Open the
resolvers.js
file in thesubgraph-reviews
directory.Add a
Location
entry to theresolvers
map. We'll also add two empty resolver functions for theoverallRating
andreviewsForLocation
fields.subgraph-reviews/resolvers.jsconst resolvers = {Query: {// ...},Location: {overallRating: () => {},reviewsForLocation: () => {},},Review: {// ...},Mutation: {// ...},};We'll start with the
overallRating
resolver. First, we'll destructure theparent
argument (aLocation
object) to get theid
field. We'll also destructure thecontextValue
argument to pull out ourdataSources
.subgraph-reviews/resolvers.jsoverallRating: ({id}, _, {dataSources}) => {// TODO},Inside the function, we'll return the results of calling our
dataSources
object, itsReviewsAPI
, and itsgetOverallRatingForLocation
method. Then, pass in theid
of the location that we're querying.subgraph-reviews/resolvers.jsoverallRating: ({id}, _, {dataSources}) => {return dataSources.reviewsAPI.getOverallRatingForLocation(id);},Note: You can check out how the
getOverallRatingForLocation
method works by peeking inside thesubgraph-reviews/datasources/ReviewsApi.js
file.Next, we'll set up the resolver function for the
reviewsForLocation
field and follow the same structure as before. This time, we'll use thegetReviewsForLocation
method of theReviewsAPI
to fetch all reviews for a location based on its id.subgraph-reviews/resolvers.jsreviewsForLocation: ({id}, _, {dataSources}) => {return dataSources.reviewsAPI.getReviewsForLocation(id);},
Wonderful! Our resolvers receive a location's id and can return the right data for that location.
Adding a __resolveReference
function
Earlier, we learned that each subgraph that contributes fields to an entity needs to define a reference resolver for that entity.
We already defined the reference resolver in the locations
subgraph, but the reviews
subgraph also needs some way of knowing which particular location object it's resolving fields for.
Here's the good news: because we're using Apollo Server, defining the reference resolver function explicitly in the reviews
subgraph is not a requirement. Apollo Server defines a default reference resolver for any entities we don't define one for.
This diagram shows how the __resolveReference
function works by default with a query for a particular Location
.
- A queried location is resolved in the
locations
subgraph based on itsid
argument. - When the server reaches the
reviewsForLocation
field, the router knows that this is the responsibility of thereviews
subgraph. The__resolveReference
function receives the queriedLocation
object that thelocations
subgraph returned. - The
reviewsForLocation
resolver receives the referencedLocation
object as itsparent
argument, which it can then destructure and use to resolve data.
Even with this feature working for us under the hood, we'll walk through the steps to add this function ourselves in the optional section below, and review what happens when our router associates data across subgraphs.
So are we ready to put our supergraph to the test? Not so fast! Remember we made schema changes! These changes need to be published to the registry, or we'll run into the same problem we faced in the last lesson.
So we'll run the rover subgraph publish
command, passing it the values for our reviews
subgraph.
rover subgraph publish <APOLLO_GRAPH_REF> \--name reviews \--schema ./subgraph-reviews/reviews.graphql
Querying data across subgraphs
With a successful publish, let's return to Studio and refresh the Explorer. We can see that our list of subfields now includes overallRating
and reviewsForLocation
!
Let's include these fields in a new query to our router. We'll use the query the client needs for the location details page.
query GetLocationDetails($locationId: ID!) {location(id: $locationId) {idnamedescriptionphotooverallRatingreviewsForLocation {idcommentrating}}}
In the Variables panel:
{ "locationId": "loc-1" }
Look at this sweet sweet data!
The client is going to be thrilled that they're getting information about a location from both subgraphs without having to do any work to put it all together themselves!
Even better, we didn't have to restart our router. This is thanks to our router's connection to Apollo Uplink. Our newly published subgraph triggered GraphOS to compose a new supergraph schema. Our router then polled the Uplink and fetched the new supergraph schema. Best of all, the router started to use the new supergraph schema immediately, enabling us to query the new fields right away!
With that, we can finally check off the last two fields of our schema agreement!
Practice
Drag items from this box to the blanks above
delete
key field
search
concerns
create
types
Uplink
endpoints
router
entities
assignments
You're working on a federated graph that manages book information. The authors
subgraph defines an Author
entity with a primary key field id
of non-nullable type ID
. You want to use the Author
entity in the books
subgraph. Define the Author
entity below, and add a new field, books
, which returns a non-nullable list of non-nullable type Book
.
Key takeaways
- A subgraph that contributes fields to an entity should define the following:
- The entity, using the
@key
directive and its primary key fields, as well as the new fields the subgraph defines - A
__resolveReference
function to know which particular entity instance a subgraph is resolving fields for. This can be taken care of by default by Apollo Server.
- The entity, using the
- A federated architecture helps organize and illustrate the relationships between types across our graph in a way that an app developer (or multiple teams of developers!) would want to consume the data.
- When both subgraphs use the same primary key to associate data for a type, the router coordinates data from both sources and bundles it up in a single response.
Up next
Congratulations, you've finished implementing all the fields in the FlyBy graph!
In the next and final lesson, we'll put the backend and frontend together and finally see FlyBy working in a browser!
Share your questions and comments about this lesson
Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.