9. Reference resolvers
10m

Overview

We added the User interface as well as the Host and Guest entities to the accounts subgraph , but they need accompanying s! 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 s to an entity use a special called a reference resolver to resolve entities. This function enables the to directly access entity s that each subgraph contributes.

We'll implement reference s for the Host and Guest entities in the accounts subgraph.

  1. Open up the resolvers.js file in the accounts subgraph, and delete the default query that came with the template, along with its example function.

    const resolvers = {
    - Query: {
    - example: () => 'Hello World!',
    - },
    };
  2. Inside the resolvers object, add a reference for the Host type:

    subgraph-accounts/resolvers.js
    Host: {
    __resolveReference: (user, { dataSources }) => {
    return dataSources.accountsAPI.getUser(user.id);
    },
    },

    We're using the accountsAPI 's getUser method and passing it the id of the user to retrieve that user's information.

    We haven't connected the accountsAPI yet, but we will later on in the lesson!

  3. Let's add a similar reference for the Guest type.

    subgraph-accounts/resolvers.js
    Guest: {
    __resolveReference: (user, { dataSources }) => {
    return dataSources.accountsAPI.getUser(user.id);
    },
    },
  4. The full resolvers map should now look like this:

    subgraph-accounts/resolvers.js
    const 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 s! These s are used when the 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 s in the monolith subgraph that will need to reference the Host and Guest entities. For these s, we're going to jump to their corresponding s and have them return an entity representation.

If you recall from Voyage I, an entity representation is an object that the uses to represent a specific entity from another subgraph. It contains a __typename property and values for the entity's primary key s. In our case, the primary key for both Host and Guest is id.

We'll return an entity representation for each the following s in the monolith subgraph:

  • Listing.host, which returns a Host type
  • Booking.guest, which returns a Guest type
  • Review.author, which returns a User type

To get started, open the resolvers.js file in the monolith directory.

✏️ Listing.host

Let's find the Listing.host . We'll remove the body of the function, where it currently calls the accountsAPI . We now want this to return a representation of the Host entity.

monolith/resolvers.js
Listing: {
host: ({ hostId }) => {
return { id: hostId };
},
}

Here we've destructured the parent (which is a Listing object) to retrieve the hostId property. This hostId is used as the value for the primary key id.

Note: We don't need to include the __typename in this entity representation. The automatically knows how to get the __typename (because __typename is a meta- that's included on all objects, per the GraphQL specification).

✏️ Booking.guest

Let's do the same for the Booking.guest . Find the in the resolvers.js file and replace the body of the function so that it returns an object as the entity representation.

monolith/resolvers.js
Booking: {
// other Booking resolvers
guest: ({ guestId }) => {
return { id: guestId };
},
}

Similar to the Listing.host , here we've destructured the parent (a Booking object) to retrieve the guestId property. This guestId is used as the value for the primary key id.

✏️ Review.author

Finally, we have the Review.author , which we'll handle slightly differently.

In the , the Review.author returns a User interface, which is an abstract type. So the Review.author 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 in the resolvers.js file and replace it with the following:

monolith/resolvers.js
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 of the (parent) to review to clarify the 's type.

And that's it for all the s that return representations of entities! There's just one more we need: __resolveType.

Resolving the interface

We need a resolveType 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 should only belong in the accounts s, so delete it from the monolith s.

subgraph-accounts/resolvers.js
User: {
__resolveType(user) {
return user.role;
}
},

Note: Make sure you delete the __resolveType function from the monolith s.

monolith/resolvers.js
- User: {
- __resolveType(user) {
- return user.role;
- }
- },

That's all the s we need for our subgraphs to handle our changes!

Creating data sources

Currently, our accounts subgraph doesn't have any s connected yet. However, we know that it needs the accountsAPI , because we've been using it in our s. Let's tackle that next.

  1. Let's look at the datasources/accounts.js file in the monolith directory. This is the we want to use in the accounts subgraph!

  2. Copy the datasources/accounts.js file and paste it into the subgraph-accounts/datasources directory. This lets us access the in the accounts subgraph!

    You can also delete the placeholder datasources.js file in the subgraph-accounts/datasources directory while you're here.

  3. Open up subgraph-accounts/index.js and add the import at the top:

    subgraph-accounts/index.js
    const AccountsAPI = require("./datasources/accounts");
  4. Then, find the line where we return the contextValue object for s to use. We'll set up the AccountsAPI inside the dataSources object.

    subgraph-accounts/index.js
    return {
    ...userInfo,
    dataSources: {
    accountsAPI: new AccountsAPI(),
    },
    };
  5. Let's not forget to incude the cache when we initialize the AccountsAPI since it's a RESTDataSource class. We'll destructure the cache property from server just before we return the full contextValue object.

    subgraph-accounts/index.js
    const { cache } = server;
    return {
    ...userInfo,
    dataSources: {
    accountsAPI: new AccountsAPI({ cache }),
    },
    };

Testing our changes

Let's test whether our reference s our working! Try out this query in the :

query GetListing {
listing(id: "listing-1") {
host {
name
profilePicture
profileDescription
}
reviews {
rating
author {
id
name
}
}
}
}

This query retrieves a listing's host information (testing out our Listing.host ) 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 ).

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 {
id
name
profilePicture
}
}

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 belonged to the accounts subgraph. And right now, the me in our GetMyProfile query above still belongs to the monolith subgraph! The is asking the monolith subgraph to resolve the , when really it should be asking the accounts subgraph.

We'll fix this in the next lesson.

Practice

Use this 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: Int
isInColor: Boolean
}
type YoungAdultNovel implements Book {
isbn: ID!
title: String!
genre: String!
wordCount: Int
numberOfChapters: Int
}
type LibraryMember @key(fields: "id") {
id: ID! @external
faveBook: Book!
}
Code Challenge!

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 s it's responsible for.

Previous
Next