4. Using a data loader
10m

Overview

Our data loader is ready to batch together our artist IDs and handle the request to the REST endpoint. It's considered officially "registered" in our application, but not yet being used anywhere.

In this lesson, we will:

  • Update our datafetcher to delegate responsibility to the data loader

Using our data loader

We've taken care of the first two steps in our plan to bring data loaders into our application.

  1. We created a data loader class.
  2. We gave our class a load method.

Now we'll update our datafetcher method for the Track.artist . Rather than calling our SpotifyClient methods directly, it will delegate responsibility to the data loader to gather up, and provide data for, all of the necessary track artists in our .

Step 3 - Updating our datafetcher

Back in datafetchers/TrackDataFetcher, we have a few changes to make.

Let's start with some additional imports we'll need.

datafetchers/TrackDataFetcher
import org.dataloader.DataLoader;
import java.util.concurrent.CompletableFuture;

First, we need to update the return type of the getArtist method. It no longer returns a single MappedArtist, but rather a CompletableFuture that accepts MappedArtist as a type .

datafetchers/TrackDataFetcher.java
@DgsData(parentType="Track", field="artist")
public CompletableFuture<MappedArtist> getArtist(DgsDataFetchingEnvironment dfe) {
MappedTrack track = dfe.getSource();
String artistId = track.getArtistId();
return spotifyClient.artistRequest(artistId);
}

Now, how do we use our datafetcher? We can access any registered data loader in our application by calling the dfe.getDataLoader() method, and providing a name.

Let's extract our "artists" data loader into a called artistDataloader, and give it the appropriate type of DataLoader<String, MappedArtist>, to match the parameters our data loader expects.

datafetchers/TrackDataFetcher.java
@DgsData(parentType="Track", field="artist")
public CompletableFuture<MappedArtist> getArtist(DgsDataFetchingEnvironment dfe) {
MappedTrack track = dfe.getSource();
String artistId = track.getArtistId();
DataLoader<String, MappedArtist> artistDataloader = dfe.getDataLoader("artists");
return spotifyClient.artistRequest(artistId);
}

Finally, we'll replace the return value. We're shifting responsibility for loading the artists' data to the dataloader instead of spotifyClient directly, so we'll call the load method of our artistDataloader, passing in the artistId.

datafetchers/TrackDataFetcher.java
@DgsData(parentType="Track", field="artist")
public CompletableFuture<MappedArtist> getArtist(DgsDataFetchingEnvironment dfe) {
MappedTrack track = dfe.getSource();
String artistId = track.getArtistId();
DataLoader<String, MappedArtist> artistDataloader = dfe.getDataLoader("artists");
- return spotifyClient.artistRequest(artistId);
+ return artistDataloader.load(artistId);
}

Stepping back, we can see the new flow of this Track.artist datafetcher. Before, it called our in a new request for every artistId it received. Now, it passes each artistId through to our data loader class, which does the work of batching all of the IDs it receives together in a single request.

This lets the datafetcher continue to fulfill its regular responsibilities—namely, being called for each instance of Track.artist it's meant to help resolve—without bogging down performance with multiple network calls. Instead, it just passes each ID onto the data loader, and DGS takes care of the rest!

Running a query

We've made a lot of changes, so let's stop our running server and re it.

Press the play button, or run the following command.

./gradlew bootRun

Let's jump back into Sandbox and make sure that our running server is connected.

https://studio.apollographql.com/sandbox/explorer

A screenshot of the Apollo Sandbox Explorer, highlighting the connection input with the locally running server's address

We'll run the same as before, keeping our eyes on the application terminal so we can monitor the number of requests being made through the messages we logged.

A query for a playlist, tracks, and artists
query GetPlaylist($playlistId: ID!) {
playlist(id: $playlistId) {
id
name
tracks {
id
name
durationMs
artist {
id
name
followers
genres
uri
}
}
}
}

And in the Variables panel:

{
"playlistId": "6Fl8d6KF0O4V5kFdbzalfW"
}

When we run the , we should see just one line printed out in our terminal:

Terminal output
I am making a call to the artists endpoint with artists
[3GBPw9NK25X1Wt2OUvOwY3, 33QmoCkSqADuQEtMCysYLh, 6H1RjVyNruCmrBEWRbD0VZ,
2JY5qzEozvTdogkDTkkOMf, 3WrFJ7ztbogyGnTHbHJFl2, 4x1nvY2FN8jxqAFA0DA02H,
0IF46mUS8NXjgHabxk2MCM, 7J77vP6EbI63lB3snZeRj4, 0jRqFvRKCDryHOgrgFqsKG,
4WhgWgXNi0OM2bmTCnj2ws, 6yJCxee7QumYr820xdIsjo]

Our artist IDs have successfully been batched together in a single request! 👏👏👏

The benefit of DGS data loaders

In addition to batching all of our artist IDs together in a single request to the REST API, there's an additional benefit to using data loaders from DGS. By default, the latest version of DGS will take care of caching when we pass multiple identifiers to our data loader's load method. This means that even if it receives the same artist ID multiple times, the ultimate request the data loader makes will only include it once.

Let's try this out with another that asks for a different playlist. This particular playlist includes multiple songs by the same artist, so we should see in our terminal output that the artist's ID is included just once in the request.

Keeping the same in Sandbox, update the Variables panel with the following ID.

The specific playlist's ID
{
"playlistId": "5evmObkq06UCWmtlcxK4Ev"
}

Run the , then check out the terminal output. You should see a message like the one below!

I am making a call to the artists endpoint with artists [3WrFJ7ztbogyGnTHbHJFl2]

Just one instance of the artist ID—the others, detected as duplicates, have been removed!

Practice

Which of the following statements describes how a data loader works with a datafetcher?

Key takeaways

  • To actually use a data loader, we call its load method in a datafetcher method.
  • The DgsDataFetchingEnvironment parameter available to every datafetcher method includes a getDataLoader method.
  • DGS' built-in data loaders come with caching benefits, and will deduplicate the keys they make requests for.

Congratulations!

And with that, you've done it! You've tackled the n+1 problem—with data loaders in your toolbelt, you have the resources to boost your datafetchers' efficiency. We've taken a basic data fetching strategy in our application, and made it into something that can scale with our queries and features. By employing data loaders, we've seen how we can make fewer, more efficient requests across the network, delivering up data much faster than we ever could have before.

Next up: learn how to grow your API with an entirely new domain in Federation with Java & DGS. We'll cover the best practices of building a federated , along with the tools you'll use to make the path toward robust enterprise APIs smooth, observable, and performant!

Thanks for joining us in this course, and we can't wait to see you in the next.

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.