Overview
We're finally ready to add the recommendedPlaylists
field to a Recipe
!
In this lesson, we will:
- Learn how multiple subgraphs can contribute fields to an entity
Defining an entity
The Recipe
type is already marked as an entity in the recipes
subgraph. We can confirm this by taking a look at the Schema page in Studio, and selecting Objects.
Beside the Recipe
type is an E
label, denoting that it's an entity type. Clicking into the type, we can further see the key fields for this entity: id
.
This is great! The recipes
subgraph has already opened us up to contribute fields to this entity. Let's go ahead and do that, jumping back to our project in the code editor.
Let's create a new class in the Types
folder called Recipe
.
namespace Odyssey.MusicMatcher;public class Recipe{}
Remember, to define an entity, we need two things: a primary key ([Key]
) and a reference resolver ([ReferenceResolver]
).
Defining a primary key
First, let's import the
HotChocolate.ApolloFederation
package at the top of the file.Types/Recipe.csusing HotChocolate.ApolloFederation;Inside the
Recipe
class, we'll add theId
as a property of the class, using short-hand syntax for auto-implemented properties. Note that we're using uppercaseId
here to follow C# convention, but behind the scenes, Hot Chocolate will convert it to lowercase when generating the GraphQL schema. This property returns astring
type.Types/Recipe.cspublic string Id { get; }We'll also annotate this property with the
[ID]
attribute. Though theId
returns astring
type, we want to map it to a GraphQL scalar type forID
, notString
.Types/Recipe.cs[ID]public string Id { get; }Finally, we'll tag the property with the
Key
attribute, which specifies it as the primary key.Types/Recipe.cs[ID][Key]public string Id { get; }
Defining the reference resolver function
Along with the key, we need a reference resolver: a function that returns a particular instance of an entity.
Let's define a new function, which will be
public static
. We'll call itGetRecipeById
. The name of the reference resolver doesn't matter, but we should be descriptive about what it's doing. This function will return aRecipe
type (the entity type!).Types/Recipe.cspublic static Recipe GetRecipeById(){}To mark this as the reference resolver for the entity, we'll use the
[ReferenceResolver]
attribute.Types/Recipe.cs[ReferenceResolver]public static Recipe GetRecipeById()For the parameters of this function, we have access to the key field(s) for the entity. In this case, that's the
id
property.Types/Recipe.cspublic static Recipe GetRecipeById(string id)In the body of the function, we'll return an instance of the
Recipe
class, using theid
to instantiate it.Types/Recipe.csreturn new Recipe(id);Finally, we'll write the constructor for the
Recipe
class, which takes in astring id
and assigns it to theId
property.Types/Recipe.cspublic Recipe(string id){Id = id;}
Contributing to an entity
Awesome, we have the stub of the entity filled out: the bare minimum needed to define an entity in a new subgraph. Now let's add the new field to make our dream query come true: recommended playlists for a recipe.
Inside the body of the
Recipe
class, add a new resolver function calledRecommendedPlaylists
. For now, we'll work with hard-coded data. We'll replace it with a call to our data source in the next lesson.The
RecommendedPlaylists
method will return a list ofPlaylist
objects.Types/Recipe.cspublic List<Playlist> RecommendedPlaylists(){return new List<Playlist>{new Playlist("1", "GraphQL Groovin'"),new Playlist("2", "Graph Explorer Jams"),new Playlist("3", "Interpretive GraphQL Dance")};}Let's also add a description for this field.
Types/Recipe.cs[GraphQLDescription("A list of recommended playlists for this particular recipe. Returns 1 to 3 playlists.")]
Registering the entity
One last thing: we'll need to register this type in our GraphQL server. Open up Program.cs
and find the line where we initialize the GraphQL server. To group all our similar method calls, we'll add the next line right after registering the other types (such as Query
and Mutation
): AddType<Recipe>()
.
builder.Services.AddGraphQLServer().AddApolloFederation().AddQueryType<Query>().AddMutationType<Mutation>().AddType<Recipe>().RegisterService<SpotifyService>();
Alright, let's save our changes and restart the server.
The rover dev
process will pick up the changes and take care of composing those changes with the recipes
subgraph. If all goes well, we're ready to query this new schema!
Querying in Explorer
Jump back over to http://localhost:4000, where our local rover dev
router is running.
Let's start building a new operation, a pared down version of our dream query. We'll ask for a specific recipe's name and the names of its recommended playlists.
query GetRecipeWithPlaylists {randomRecipe {namerecommendedPlaylists {idname}}}
Woohoo–we're getting data back–playlists to sing along to while cooking up our recipe, nice!
Let's take a peek at the query plan. Click on the dropdown arrow beside Response and select Query Plan.
The chart view shows an initial fetch to the recipes
subgraph, followed by another fetch to the soundtracks
subgraph before finally merging (or flattening) the recipe
type. Click Show plan as text.
QueryPlan {Sequence {Fetch(service: "recipes") {{recipe(id: "rec3j49yFpY2uRNM1") {__typenameidname}}},Flatten(path: "recipe") {Fetch(service: "soundtracks") {{... on Recipe {__typenameid}} =>{... on Recipe {recommendedPlaylists {idname}}}},},},}
We get a little more detail here. First, the router sends an operation to the recipes
subgraph for the recipe's __typename
, id
and name
. We didn't include __typename
or id
in our original operation, but the router needs them for its next step in the query plan to build the entity representation of a Recipe
!
The router sends that entity representation to the soundtracks
subgraph, which uses the information to identify that instance of a Recipe
and contribute further fields to it: specifically, the recommendedPlaylists
field.
Awesome! Our recipes
and soundtracks
services are now collaborating on the same Recipe
entity. Each subgraph contributes its own fields, and the router packages up the response for us.
Practice
ReferenceResolver
.Key takeaways
- In Hot Chocolate, we use the
[Key]
attribute to define the key field for an entity and the[ReferenceResolver]
attribute to define the reference resolver function. - In Hot Chocolate, entities need to be registered to the GraphQL server.
Up next
Our musical recommendations are still pretty weak. In fact, they're not much like recommendations at all—they're hardcoded playlist objects! We haven't actually used any recipe-specific data to determine the playlists we recommend to our potential chef users. In the next lesson, we'll dive deeper into federation-specific directives and fortify our soundtrack suggestions.
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.