7. Consuming a datasource
10m

Overview

With a new datasource class wired up and ready to be called, we can connect some dots in our datafetcher method.

In this lesson, we will:

  • Connect our datafetcher to our datasource
  • Account for unfamiliar JSON properties in the REST API response
  • Map JSON objects into Java classes we can manipulate and reason about

Consuming a datasource

We now have an easy way to call out to the Spotify API for featured playlist data and return it in a PlaylistCollection instance.

datasources/SpotifyClient
public PlaylistCollection featuredPlaylistsRequest() {
return client
.get()
.uri("/browse/featured-playlists")
.retrieve()
.body(PlaylistCollection.class);
}

So...will running this return real data?

query FeaturedPlaylists {
featuredPlaylists {
id
name
description
}
}

Not quite! When this is executed, the first thing DGS does is look for the corresponding featuredPlaylists datafetcher method. In this case, that's the featuredPlaylists method inside of PlaylistDataFetcher.

Of course, this method is still just returning hardcoded data. It can't call any of the methods in SpotifyClient because it doesn't know about it yet. Quite the dead end!

datafetchers/PlaylistDataFetcher
@DgsQuery
public List<MappedPlaylist> featuredPlaylists() {
MappedPlaylist rockPlaylist = new MappedPlaylist();
rockPlaylist.setId("1");
rockPlaylist.setName("Rock n' Roll");
rockPlaylist.setDescription("A rock n' roll playlist");
MappedPlaylist popPlaylist = new MappedPlaylist();
popPlaylist.setId("2");
popPlaylist.setName("Pop");
popPlaylist.setDescription("A pop playlist");
return List.of(rockPlaylist, popPlaylist);
}

Let's jump back into datafetchers/PlaylistDataFetcher to fix that.

Using SpotifyClient in the datafetcher

We'll import SpotifyClient, and give our class a spotifyClient property. Then, we'll include a constructor that wires everything up on the class.

PlaylistDataFetcher
// other imports
import com.example.soundtracks.datasources.SpotifyClient;
import org.springframework.beans.factory.annotation.Autowired;
@DgsComponent
public class PlaylistDataFetcher {
private final SpotifyClient spotifyClient;
@Autowired
public PlaylistDataFetcher(SpotifyClient spotifyClient) {
this.spotifyClient = spotifyClient;
}
// featuredPlaylists method
}

In our class' featuredPlaylists method (the one that DGS is going to call for the Query.featuredPlaylists !), we'll first clean up our mocks.

@DgsQuery
public List<MappedPlaylist> featuredPlaylists() {
- MappedPlaylist rockPlaylist = new MappedPlaylist();
- rockPlaylist.setId("1");
- rockPlaylist.setName("Rock n' Roll");
- rockPlaylist.setDescription("A rock n' roll playlist");
- MappedPlaylist popPlaylist = new MappedPlaylist();
- popPlaylist.setId("2");
- popPlaylist.setName("Pop");
- popPlaylist.setDescription("A pop playlist");
-
- return List.of(rockPlaylist, popPlaylist);
}

Next, we'll call the featuredPlaylistsRequest method our class' spotifyClient instance.

@DgsQuery
public List<MappedPlaylist> featuredPlaylists() {
spotifyClient.featuredPlaylistsRequest();
}

We know that featuredPlaylistsRequest returns data in a PlaylistCollection instance, so let's import it:

import com.example.soundtracks.models.PlaylistCollection;

And apply it to the response we get from calling our SpotifyClient instance.

@DgsQuery
public List<MappedPlaylist> featuredPlaylists() {
PlaylistCollection response = spotifyClient.featuredPlaylistsRequest();
}

Next, we want to actually get the list of playlists from the response. We wrote our PlaylistCollection class' getPlaylists method to grab this data exactly.

@DgsQuery
public List<MappedPlaylist> featuredPlaylists() {
PlaylistCollection response = spotifyClient.featuredPlaylistsRequest();
return response.getPlaylists();
}

Time to return to Sandbox! First, let's make sure that we restart our server so that we're running the latest changes.

Task!

Now let's navigate back to the Explorer. Check that your connection to http://localhost:8080/graphql is still valid, then let's rerun our .

Aaaaand, drumroll...

query FeaturedPlaylists {
featuredPlaylists {
id
name
description
}
}

UH-OH! Another error!

Here's the message in our terminal:

"JSON parse error: Unrecognized field "collaborative"
(class com.example.soundtracks.models.MappedPlaylist), not marked as ignorable"

We're seeing this error because our MappedPlaylist class, inheriting from the generated Playlist class, only knows how to handle three specific fields: id, name, and description.

But we're attempting to take a list of much larger JSON objects, with many more properties, and convert each into an instance of the MappedPlaylist class. When we inspect one of these playlist objects from our REST endpoint, we can see that "collaborative" is its very first property. But our MappedPlaylist class doesn't know what to do with it!

The JsonIgnoreProperties annotation

The Jackson package gives us an annotation—JsonIgnoreProperties—to handle this very scenario. By setting @JsonIgnoreProperties on a class, and passing it an of ignoreUnknown = true, we can instruct our class to ignore any unfamiliar JSON properties it encounters, and handle what it does recognize.

@JsonIgnoreProperties(ignoreUnknown = true)
public class HappilyIgnoresUnknownProperties {
// class properties, getters, setters
}

Let's apply this annotation to our MappedPlaylist class to take care of the error.

models/MappedPlaylist
// other imports
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class MappedPlaylist extends Playlist {
}

And with that, let's again restart our server to see the effect of adding this annotation.

Task!

Run the query, fetch the data!

Back in the Explorer, let's try that again.

query FeaturedPlaylists {
featuredPlaylists {
id
name
description
}
}

In the Response panel, we should now see actual data from our REST API!

{
"data": {
"featuredPlaylists": [
{
"id": "6Fl8d6KF0O4V5kFdbzalfW",
"name": "Sweet Beats & Eats",
"description": "Tooth-achingly sweet beats for your sweet eats"
},
{
"id": "20RU4pHDte01QywpOL6ifh",
"name": "Grilling Tunes",
"description": "Set the barbecue mood. Upbeat and laid-back tracks complement the sizzle of the grill, turning your outdoor cooking sessions into a flavorful experience. For those who savor good music and great food, it&#x27;s the perfect playlist"
},
{
"id": "6LB6g7S5nc1uVVfj00Kh6Z",
"name": "Zesty Culinary Harmony",
"description": "Infuse flavor into your kitchen. This playlist merges zesty tunes with culinary vibes, creating a harmonious background for your cooking escapades. Feel the synergy between music and the zest of your creations."
}
]
}
}

Key takeaways

  • Datafetcher methods can connect directly to the datasources in our API
  • We can extend the generated classes DGS gives us with additional logic to better suit the needs of our API
  • The JsonIgnoreProperties annotation allows us to ignore unfamiliar JSON properties when instantiating a new class

Up next

Amazing! We've translated the REST API response to the types that we defined in the schema, and we have a seamless experience. But right now, our list of playlists is pretty limited. Based on our mockups, we need to start exploring some additional functionality—such as querying for additional playlist properties (hello, tracks!) or asking for a particular playlist. Let's uplevel our API in the next lesson.

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.