9. Contributing fields to an entity
5m

Overview

We're finally ready to add the recommendedPlaylists to a Recipe!

In this lesson, we will:

  • Learn how multiple can contribute to an

Defining an entity

The Recipe type is already marked as an in the recipes . We can confirm this by taking a look at the Schema page in Studio, and selecting Objects.

https://studio.apollographql.com

Schema - Objects page

Beside the Recipe type is an E label, denoting that it's an type. Clicking into the type, we can further see the key for this entity: id.

This is great! The recipes has already opened us up to contribute to this . 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.

Types/Recipe.cs
namespace Odyssey.MusicMatcher;
public class Recipe
{
}

Remember, to define an , we need two things: a primary key ([Key]) and a reference ([ReferenceResolver]).

Defining a primary key

  1. First, let's import the HotChocolate.ApolloFederation package at the top of the file.

    Types/Recipe.cs
    using HotChocolate.ApolloFederation;
  2. Inside the Recipe class, we'll add the Id as a property of the class, using short-hand syntax for auto-implemented properties. Note that we're using uppercase Id here to follow C# convention, but behind the scenes, Hot Chocolate will convert it to lowercase when generating the . This property returns a string type.

    Types/Recipe.cs
    public string Id { get; }
  3. We'll also annotate this property with the [ID] attribute. Though the Id returns a string type, we want to map it to a type for ID, not String.

    Types/Recipe.cs
    [ID]
    public string Id { get; }
  4. 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 : a function that returns a particular instance of an .

  1. Let's define a new function, which will be public static. We'll call it GetRecipeById. The name of the reference doesn't matter, but we should be descriptive about what it's doing. This function will return a Recipe type (the type!).

    Types/Recipe.cs
    public static Recipe GetRecipeById()
    {
    }
  2. To mark this as the reference for the , we'll use the [ReferenceResolver] attribute.

    Types/Recipe.cs
    [ReferenceResolver]
    public static Recipe GetRecipeById()
  3. For the parameters of this function, we have access to the key (s) for the . In this case, that's the id property.

    Types/Recipe.cs
    public static Recipe GetRecipeById(
    string id
    )
  4. In the body of the function, we'll return an instance of the Recipe class, using the id to instantiate it.

    Types/Recipe.cs
    return new Recipe(id);
  5. Finally, we'll write the constructor for the Recipe class, which takes in a string id and assigns it to the Id property.

    Types/Recipe.cs
    public Recipe(string id)
    {
    Id = id;
    }

Contributing to an entity

Awesome, we have the stub of the filled out: the bare minimum needed to define an entity in a new . Now let's add the new to make our dream come true: recommended playlists for a recipe.

  1. Inside the body of the Recipe class, add a new function called RecommendedPlaylists. For now, we'll work with hard-coded data. We'll replace it with a call to our in the next lesson.

    The RecommendedPlaylists method will return a list of Playlist objects.

    Types/Recipe.cs
    public List<Playlist> RecommendedPlaylists()
    {
    return new List<Playlist>
    {
    new Playlist("1", "GraphQL Groovin'"),
    new Playlist("2", "Graph Explorer Jams"),
    new Playlist("3", "Interpretive GraphQL Dance")
    };
    }
  2. Let's also add a description for this .

    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 . Open up Program.cs and find the line where we initialize the . 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>().

Program.cs
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 . If all goes well, we're ready to this new schema!

Querying in Explorer

Jump back over to http://localhost:4000, where our local rover dev is running.

Let's start building a new , a pared down version of our dream . We'll ask for a specific recipe's name and the names of its recommended playlists.

query GetRecipeWithPlaylists {
randomRecipe {
name
recommendedPlaylists {
id
name
}
}
}
http://localhost:4000

Local router, query response

Woohoo–we're getting data back–playlists to sing along to while cooking up our recipe, nice!

Let's take a peek at the . Click on the dropdown arrow beside Response and select Query Plan.

The chart view shows an initial fetch to the recipes , followed by another fetch to the soundtracks before finally merging (or flattening) the recipe type. Click Show plan as text.

http://localhost:4000

Local router, query plan

Query plan
QueryPlan {
Sequence {
Fetch(service: "recipes") {
{
recipe(id: "rec3j49yFpY2uRNM1") {
__typename
id
name
}
}
},
Flatten(path: "recipe") {
Fetch(service: "soundtracks") {
{
... on Recipe {
__typename
id
}
} =>
{
... on Recipe {
recommendedPlaylists {
id
name
}
}
}
},
},
},
}

We get a little more detail here. First, the sends an to the recipes for the recipe's __typename, id and name. We didn't include __typename or id in our original , but the needs them for its next step in the to build the representation of a Recipe!

The sends that representation to the soundtracks , which uses the information to identify that instance of a Recipe and contribute further to it: specifically, the recommendedPlaylists .

Awesome! Our recipes and soundtracks services are now collaborating on the same Recipe . Each contributes its own , and the packages up the response for us.

Practice

True or false: In Hot Chocolate, the reference resolver function must be named ReferenceResolver.

Key takeaways

  • In Hot Chocolate, we use the [Key] attribute to define the key for an and the [ReferenceResolver] attribute to define the reference function.
  • In Hot Chocolate, entities need to be registered to the .

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 and fortify our soundtrack suggestions.

Previous

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.