12. Wrapping up
10m

Overview

In the last lesson, we saw how our successfully responds with the code, success, and message properties on the AddItemsToPlaylistPayload. The finish line is in sight!

In this lesson, we will:

  • Complete our and return data about the Playlist we modified
  • Construct lean datafetcher methods by delegating responsibility for follow-up REST requests

Completing our mutation response

Our runs, but we're still not returning a complete Playlist object—this is because our datafetcher only knows about a particular playlist's id. Even though we do have an actual Playlist instance, most of its properties are currently null!

To get more details about the Playlist we've updated, we need to use the id we have to make a separate call: to the /playlists/{playlist_id} endpoint we explored earlier. But there's an important reason why we won't make this call from inside the same addItemsToPlaylist datafetcher method.

Keeping datafetchers lean

Throughout this course, we've praised for the ability to fetch exactly the data we ask for: nothing more, nothing less. And as we saw in the last lesson, we could very well choose to submit an that asks for just three of the four that exist on the AddItemsToPlaylistPayload type.

mutation AddTracksToPlaylist($input: AddItemsToPlaylistInput!) {
addItemsToPlaylist(input: $input) {
code
success
message
}
}

If we ask our addItemsToPlaylist datafetcher method to make an additional call to the /playlists/{playlist_id} endpoint, it will run this logic every time it's invoked. This could lead to a lot of unnecessary network requests! An like the one above might not ask for any playlist data, yet the datafetcher would still go to all the trouble of fetching those details!

We only want to call out to the /playlists/{playlist_id} endpoint when our actually includes the playlist and any of its subfields. To make this work—you guessed it!—we'll take advantage of chains again.

Resolver chains in review

Let's use the below as an example.

mutation AddTracksToPlaylist($input: AddItemsToPlaylistInput!) {
addItemsToPlaylist(input: $input) {
code
success
message
playlist {
name
}
}
}

The first datafetcher method to be called will be addItemsToPlaylist, which we defined specifically for the Mutation.addItemsToPlaylist . In our schema we said this field returns an AddItemsToPlaylistPayload type, which means we can include any of that type's sub in our .

We already know that the addItemsToPlaylist datafetcher has no trouble returning the values for code, success, and message. But when it reaches playlist, and its name sub, things get a little bit more complicated.

The only playlist data this method has to work with is its id, and it returns an AddItemsToPlaylistPayload object with a Playlist instance that's mostly empty.

AddItemsToPlaylistPayload{
code='200',
success='true',
message='success',
playlist='Playlist{
id='6LB6g7S5nc1uVVfj00Kh6Z',
name='null',
description='null',
tracks='null'
}'
}

We need some other method to be responsible for filling in the playlist details on the AddItemsToPlaylistPayload type. As we already discussed, this shouldn't be the responsibility of the addItemsToPlaylist datafetcher, since it will require a whole new network call to fetch all those additional details about a playlist!

So to account for the missing data on the AddItemsToPlaylistPayload.playlist , we'll define a new datafetcher method that should run anytime this field is included in a .

This means that our chain will look something like this:

Resolver chain from Mutation.addItemsToPlaylist to AddItemsToPlaylistPayload.playlist

This means that the results of calling Mutation.addItemsToPlaylist() will be available to AddItemsToPlaylistPayload.playlist(). We'll be able to use these results to know which playlist we need to request additional details for.

Let's define this new datafetcher, and explore how we get access to the results of the previous datafetcher.

The AddItemsToPlaylistPayload.playlist datafetcher

In PlaylistDataFetcher, we'll define a new method. This datafetcher will be called exclusively when a includes the AddItemsToPlaylistPayload.playlist . We want it to be able to receive the results of the datafetcher that runs before it, and use that data to make an additional network request.

datafetchers/PlaylistDataFetcher
public void getPayloadPlaylist() {}

This datafetcher is intended for the AddItemsToPlaylistPayload.playlist , we'll use the @DgsData annotation again.

Recall that this annotation needs to know two things: the parentType (the specific ) and the field we want the method to be responsible for.

Applied to the AddItemsToPlaylistPayload.playlist , it looks like this:

@DgsData(parentType="AddItemsToPlaylistPayload", field="playlist")
public void getPayloadPlaylist() {}

Note: Remember that if the method has the same name as the it fetches data for, you can omit the field property from the annotation.

While we're here, let's update the return type for our function, and by default return null.

@DgsData(parentType="AddItemsToPlaylistPayload", field="playlist")
public MappedPlaylist getPayloadPlaylist() {
return null;
}

Resolver chain in action

Let's update our method to receive DgsDataFetchingEnvironment as an called dfe.

PlaylistDataFetcher
// class and methods
@DgsData(parentType="AddItemsToPlaylistPayload", field="playlist")
public MappedPlaylist getPayloadPlaylist(DgsDataFetchingEnvironment dfe) {
return null;
}

At the time that getPayloadPlaylist is called, the root datafetcher for the Mutation.addItemsToPlaylist will have just been called.

Let's call getSource to access to the value that the Mutation.addItemsToPlaylist datafetcher returns.

@DgsData(parentType="AddItemsToPlaylistPayload", field="playlist")
public MappedPlaylist getPayloadPlaylist(DgsDataFetchingEnvironment dfe) {
AddItemsToPlaylistPayload payload = dfe.getSource();
return null;
}

To request data for an individual playlist, our method needs access to the playlist's id. Let's start by accessing the actual Playlist instance that was returned by the addItemsToPlaylist datafetcher.

@DgsData(parentType="AddItemsToPlaylistPayload", field="playlist")
public MappedPlaylist getPayloadPlaylist(DgsDataFetchingEnvironment dfe) {
AddItemsToPlaylistPayload payload = dfe.getSource();
Playlist playlist = payload.getPlaylist();
return null;
}

Recall that if the fails, the value of the playlist property will be null. So let's add a check here to make sure that it actually exists before we attempt to extract its id property.

@DgsData(parentType="AddItemsToPlaylistPayload", field="playlist")
public MappedPlaylist getPayloadPlaylist(DgsDataFetchingEnvironment dfe) {
AddItemsToPlaylistPayload payload = dfe.getSource();
Playlist playlist = payload.getPlaylist();
if (playlist != null) {
String playlistId = playlist.getId();
}
return null;
}

The only thing left to do is actually make that call to the /playlists/{playlist_id} endpoint!

We've encapsulated this logic in the playlistRequest method in our SpotifyClient class. So, we can add a single line of code to make this call.

if (playlist != null) {
String playlistId = playlist.getId();
return spotifyClient.playlistRequest(playlistId);
}

Let's recompile one last time, and return to Explorer to run our .

Task!
mutation AddTracksToPlaylist($input: AddItemsToPlaylistInput!) {
addItemsToPlaylist(input: $input) {
code
success
message
playlist {
id
name
description
tracks {
id
name
}
}
}
}

And in the Variables panel, add:

{
"input": {
"playlistId": "6LB6g7S5nc1uVVfj00Kh6Z",
"uris": [
"spotify:track:4iV5W9uYEdYUVa79Axb7Rh",
"spotify:track:1301WleyT98MSxVHPZCA6M"
]
}
}

And now, we should see the details for our successful including all of our updated playlist details. Bravo, you've done it!

Key takeaways

  • We should keep datafetchers as lean as possible, and delegate responsibility where possible when follow-up network requests are necessary.

Journey's end

Task!

You've built a API! You've got a working jam-packed with playlists and tracks using a REST API as a . You've written queries and , and learned some common GraphQL conventions along the way. You've explored how to use GraphQL , , and input types in your schema design. Take a moment to celebrate; that's a lot of learning!

But the journey doesn't end here! Put your newfound skills to the test in Growing your GraphQL API with Java & DGS, a hands-on lab where you'll implement a new feature from start to finish.

And when you're ready to take your API even further, jump into the next course in this series: Federation with Java & DGS.

Thanks for joining us in this course; we hope to see you in the next one!

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.