Overview
We added the User
interface as well as the Host
and Guest
entities to the accounts
subgraph schema, but they need accompanying resolvers! In this lesson, we will:
- Implement the
__resolveReference
resolver functions for the entities - Migrate over the interface's
__resolveType
resolver - Copy over the relevant data sources
- Return a representation of the entity from the
monolith
subgraph
✏️ Resolving entities
As we covered in Voyage I, subgraphs that contribute fields to an entity use a special resolver called a reference resolver to resolve entities. This function enables the router to directly access entity fields that each subgraph contributes.
We'll implement reference resolvers for the Host
and Guest
entities in the accounts
subgraph.
Open up the
resolvers.js
file in theaccounts
subgraph, and delete the default query that came with the template, along with itsexample
resolver function.const resolvers = {- Query: {- example: () => 'Hello World!',- },};Inside the
resolvers
object, add a reference resolver for theHost
type:subgraph-accounts/resolvers.jsHost: {__resolveReference: (user, { dataSources }) => {return dataSources.accountsAPI.getUser(user.id);},},We're using the
accountsAPI
data source'sgetUser
method and passing it theid
of the user to retrieve that user's information.We haven't connected the
accountsAPI
yet, but we will later on in the lesson!Let's add a similar reference resolver for the
Guest
type.subgraph-accounts/resolvers.jsGuest: {__resolveReference: (user, { dataSources }) => {return dataSources.accountsAPI.getUser(user.id);},},The full
resolvers
map should now look like this:subgraph-accounts/resolvers.jsconst resolvers = {Host: {__resolveReference: (user, { dataSources }) => {return dataSources.accountsAPI.getUser(user.id);},},Guest: {__resolveReference: (user, { dataSources }) => {return dataSources.accountsAPI.getUser(user.id);},},};
Perfect, the entities have their reference resolvers! These resolvers are used when the router sends this subgraph an entity representation. Now, let's jump over to the monolith
subgraph and check out where we can return representations of these entities.
Returning entity representations
There are a few fields in the monolith
subgraph that will need to reference the Host
and Guest
entities. For these fields, we're going to jump to their corresponding resolvers and have them return an entity representation.
If you recall from Voyage I, an entity representation is an object that the router uses to represent a specific entity from another subgraph. It contains a __typename
property and values for the entity's primary key fields. In our case, the primary key field for both Host
and Guest
is id
.
We'll return an entity representation for each the following resolvers in the monolith
subgraph:
Listing.host
, which returns aHost
typeBooking.guest
, which returns aGuest
typeReview.author
, which returns aUser
type
To get started, open the resolvers.js
file in the monolith
directory.
✏️ Listing.host
Let's find the Listing.host
resolver. We'll remove the body of the function, where it currently calls the accountsAPI
data source. We now want this resolver to return a representation of the Host
entity.
Listing: {host: ({ hostId }) => {return { id: hostId };},}
Here we've destructured the parent
argument (which is a Listing
object) to retrieve the hostId
property. This hostId
is used as the value for the primary key field id
.
Note: We don't need to include the __typename
field in this entity representation. The router automatically knows how to get the __typename
field (because __typename
is a meta-field that's included on all objects, per the GraphQL specification).
✏️ Booking.guest
Let's do the same for the Booking.guest
resolver. Find the resolver in the resolvers.js
file and replace the body of the function so that it returns an object as the entity representation.
Booking: {// other Booking resolversguest: ({ guestId }) => {return { id: guestId };},}
Similar to the Listing.host
resolver, here we've destructured the parent
argument (a Booking
object) to retrieve the guestId
property. This guestId
is used as the value for the primary key field id
.
✏️ Review.author
Finally, we have the Review.author
resolver, which we'll handle slightly differently.
In the schema, the Review.author
field returns a User
interface, which is an abstract type. So the Review.author
resolver will need to specify whether the returned type will be a Host
or a Guest
, and include it as the __typename
in its entity representation.
To determine the type, we'll need to look at business-domain-specific logic. We know that only guests can write reviews for listings and hosts. Similarly, only hosts can write reviews for guests. Knowing that, we can investigate the shape of a Review
object and determine what property might be able to help us with that logic. Here's what a review looks like:
{"id": "review-1","authorId": "user-2","targetId": "listing-1","bookingId": "booking-1","targetType": "LISTING","text": "Wow, what an experience! I've never stayed in a cave before, so I was a little unprepared. Luckily, this listing had all the amenities I needed to feel safe and prepared for anything.","rating": 4}
We can use the review's targetType
property (which will either be LISTING
, GUEST
or HOST
) to determine what the review author's type is. If a user is writing a review about a Listing or a Host, we can safely assume that user is a Guest!
Find the Review.author
resolver in the resolvers.js
file and replace it with the following:
Review: {author: (review) => {let role = '';if (review.targetType === 'LISTING' || review.targetType === 'HOST') {role = 'Guest';} else {role = 'Host';}return { __typename: role, id: review.authorId };},},
Note: We've renamed the first argument of the resolver (parent
) to review
to clarify the argument's type.
And that's it for all the resolvers that return representations of entities! There's just one more resolver we need: __resolveType
.
Resolving the interface
We need a resolveType
resolver for the User
interface! We actually already have one defined in the monolith
subgraph, so all we need to do is move that function over to the accounts
subgraph. This resolver should only belong in the accounts
resolvers, so delete it from the monolith
resolvers.
User: {__resolveType(user) {return user.role;}},
Note: Make sure you delete the __resolveType
function from the monolith
resolvers.
- User: {- __resolveType(user) {- return user.role;- }- },
That's all the resolvers we need for our subgraphs to handle our schema changes!
Creating data sources
Currently, our accounts
subgraph doesn't have any data sources connected yet. However, we know that it needs the accountsAPI
data source, because we've been using it in our resolvers. Let's tackle that next.
Let's look at the
datasources/accounts.js
file in themonolith
directory. This is the data source we want to use in theaccounts
subgraph!Copy the
datasources/accounts.js
file and paste it into thesubgraph-accounts/datasources
directory. This lets us access the data source in theaccounts
subgraph!You can also delete the placeholder
datasources.js
file in thesubgraph-accounts/datasources
directory while you're here.Open up
subgraph-accounts/index.js
and add the import at the top:subgraph-accounts/index.jsconst AccountsAPI = require("./datasources/accounts");Then, find the line where we return the
contextValue
object for resolvers to use. We'll set up theAccountsAPI
inside thedataSources
object.subgraph-accounts/index.jsreturn {...userInfo,dataSources: {accountsAPI: new AccountsAPI(),},};Let's not forget to incude the
cache
when we initialize theAccountsAPI
since it's aRESTDataSource
class. We'll destructure thecache
property fromserver
just before we return the fullcontextValue
object.subgraph-accounts/index.jsconst { cache } = server;return {...userInfo,dataSources: {accountsAPI: new AccountsAPI({ cache }),},};
Testing our changes
Let's test whether our reference resolvers our working! Try out this query in the Explorer:
query GetListing {listing(id: "listing-1") {host {nameprofilePictureprofileDescription}reviews {ratingauthor {idname}}}}
This query retrieves a listing's host information (testing out our Listing.host
resolver) and a listing's reviews. For each review, it asks for the review's rating, author's id and name (testing out our Review.author
resolver).
When you run the query, you should get back real data! Awesome! 🎉
One more thing, let's try out the query from last lesson:
query GetMyProfile {me {idnameprofilePicture}}
Make sure your Headers
are set to:
Authorization: Bearer user-1
When you run the query... uh-oh! We still get an error, but it's a new one: Abstract type "User" must resolve to an Object type at runtime for field "Query.me".
Hmm, you might be thinking: "Didn't we implement a __resolveType
resolver to take care of this?"
Well, we did, but that resolver belonged to the accounts
subgraph. And right now, the me
field in our GetMyProfile
query above still belongs to the monolith
subgraph! The router is asking the monolith
subgraph to resolve the field, when really it should be asking the accounts
subgraph.
We'll fix this in the next lesson.
Practice
Use this schema for the following code challenge:
interface Book {isbn: ID!title: String!genre: String!}type PictureBook implements Book @key(fields: "isbn") {isbn: ID!title: String!genre: String!numberOfPictures: IntisInColor: Boolean}type YoungAdultNovel implements Book {isbn: ID!title: String!genre: String!wordCount: IntnumberOfChapters: Int}type LibraryMember @key(fields: "id") {id: ID! @externalfaveBook: Book!}
Write a resolver for LibraryMember.faveBook to return a representation of the entity. You can use the book's hasPictures
property to determine if it's a PictureBook
or a YoungAdultNovel
.
Key takeaways
- Any subgraph that contributes fields to an entity needs to define a
__resolveReference
resolver function for that entity. - An entity representation is an object that includes the entity's
__typename
and@key
fields. - An interface needs a
__resolveType
resolver.
Up next
Let's finish up the accounts
subgraph by moving over the remaining Query
and Mutation
fields it's responsible for.