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.ApolloFederationpackage at the top of the file.Types/Recipe.csusing HotChocolate.ApolloFederation;Inside the
Recipeclass, we'll add theIdas a property of the class, using short-hand syntax for auto-implemented properties. Note that we're using uppercaseIdhere to follow C# convention, but behind the scenes, Hot Chocolate will convert it to lowercase when generating the GraphQL schema. This property returns astringtype.Types/Recipe.cspublic string Id { get; }We'll also annotate this property with the
[ID]attribute. Though theIdreturns astringtype, 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
Keyattribute, 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 aRecipetype (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
idproperty.Types/Recipe.cspublic static Recipe GetRecipeById(string id)In the body of the function, we'll return an instance of the
Recipeclass, using theidto instantiate it.Types/Recipe.csreturn new Recipe(id);Finally, we'll write the constructor for the
Recipeclass, which takes in astring idand assigns it to theIdproperty.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
Recipeclass, 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
RecommendedPlaylistsmethod will return a list ofPlaylistobjects.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.