13. Contributing to an entity5m

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 our reviews subgraph schema by contributing the reviewsForLocation and overallRating fields
The FlyBy schema diagram, with pencil icons next to the Location.reviews and Location.overallRating fields

✏️ Contributing fields

Remembering our FlyBy UI, we know it needs to fetch each location's overallRating, along with a list of its reviewsForLocation:

A query containing fields that will be populated by our two subgraphs, locations and reviews

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!

  1. Open up the subgraph-reviews/reviews.graphql file.

  2. Find the Location entity definition in the schema. We previously defined this as a stub of the Location type.

    subgraph-reviews/reviews.graphql
    type 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 defines name, description, and photo as fields for the Location type, we won't - and shouldn't - define those fields here in the reviews 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 the Location definition, the first thing we need to do is remove the resolvable: false property from the @key directive. This will enable our reviews subgraph to define and resolve its own Location fields.

  3. Remove the resolvable: false property from the Location type's @key directive.

    subgraph-reviews/reviews.graphql
    type Location @key(fields: "id") {
    id: ID!
    }
Task!

✏️ Adding new fields to the Location entity

Now we're ready to add the two new fields.

  • the overallRating field, which returns a Float
  • the reviewsForLocation field, which returns a non-null list of Review objects.

We'll also add descriptions to these fields so we can quickly see what they represent.

subgraph-reviews/reviews.graphql
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.

  1. Open the resolvers.js file in the subgraph-reviews directory.

  2. Add a Location entry to the resolvers map. We'll also add two empty resolver functions for the overallRating and reviewsForLocation fields.

    subgraph-reviews/resolvers.js
    const resolvers = {
    Query: {
    // ...
    },
    Location: {
    overallRating: () => {},
    reviewsForLocation: () => {},
    },
    Review: {
    // ...
    },
    Mutation: {
    // ...
    },
    };
  3. We'll start with the overallRating resolver. First, we'll destructure the parent argument (a Location object) to get the id field. We'll also destructure the context argument to pull out our dataSources.

    subgraph-reviews/resolvers.js
    overallRating: ({id}, _, {dataSources}) => {
    // TODO
    },
  4. Inside the function, we'll return the results of calling our dataSources object, its ReviewsAPI, and its getOverallRatingForLocation method. Then, pass in the id of the location that we're querying.

    subgraph-reviews/resolvers.js
    overallRating: ({id}, _, {dataSources}) => {
    return dataSources.reviewsAPI.getOverallRatingForLocation(id);
    },

    Note: You can check out how the getOverallRatingForLocation method works by peeking inside the subgraph-reviews/datasources/ReviewsApi.js file.

  5. Next, we'll set up the resolver function for the reviewsForLocation field and follow the same structure as before. This time, we'll use the getReviewsForLocation method of the ReviewsAPI to fetch all reviews for a location based on its id.

    subgraph-reviews/resolvers.js
    reviewsForLocation: ({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.

The reviews subgraph resolve reference function called for a particular queried Location
  1. A queried location is resolved in the locations subgraph based on its id argument.
  2. When the server reaches the reviewsForLocation field, the router knows that this is the responsibility of the reviews subgraph. The __resolveReference function receives the queried Location object that the locations subgraph returned.
  3. The reviewsForLocation resolver receives the referenced Location object as its parent 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!

studio.apollographql.com/graph/flyby/explorer?variant=current
The Location type with reviews fields

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) {
id
name
description
photo
overallRating
reviewsForLocation {
id
comment
rating
}
}
}

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 Apollo Studio 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!

The FlyBy schema diagram, with all the fields checked off!

Practice

Check your understanding!
In a federated graph, we should define our subgraphs based on 
 
instead of 
 
. Types containing fields that can be resolved across multiple subgraphs are called 
 
. These types always have a 
 
 that enables different subgraphs to associate data with the same object. To keep its supergraph schema up to date, our 
 
 can poll the 
 
.

Drag items from this box to the blanks above

  • delete

  • endpoints

  • create

  • concerns

  • router

  • entities

  • types

  • assignments

  • key field

Code Challenge!

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.
  • 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!

Previous
Next