5. Datafetchers
10m

Overview

But before we can our server for some listing data, we need to define exactly HOW that data should be retrieved.

In this lesson, we will:

  • Learn about datafetchers and how they work in DGS
  • Explore DGS code generation
  • Return some hard-coded mock data

πŸ›  Datafetcher first steps

We're well on our way to building a that can:

  1. Receive an incoming from our client
  2. Validate that against our schema
  3. Retrieve the data for the queried schema s
  4. And return the data as a response

We've already defined our schema, so DGS can handle steps #1 and #2 out of the box.

Our task now is to actually define how data is retrieved and returned when a is queried. We'll wrap up all of these instructions in a method called a datafetcher. (In other frameworks, you might see datafetchers described as resolver functions.)

We use datafetchers to map in our schema to logic that can fulfill them. In other words, we can write individualized methods that are called when certain schema fieldsβ€”such as our Query type's featuredListings β€”are queried.

type Query {
featuredListings: [Listing!]!
}

These methods have the responsibility of returning data in the shape that our schema expectsβ€”for example, the datafetcher we write for featuredListings should return a list of objects that match the Listing type.

Let's define a class to contain our datafetcher methods.

✏️ Writing a datafetcher

To keep our code organized, let's create a new datafetchers directory in our java/com.example.listings package.

πŸ“‚ main
┣ πŸ“‚ java
┃ ┣ πŸ“‚ com.example.listings
┃ ┃ ┃ ┣ πŸ“‚ datafetchers
┃ ┃ ┃ ┣ πŸ“„ ListingsApplication
┃ ┃ ┃ ┣ πŸ“„ WebConfiguration

Inside, we can create a new class file called ListingDataFetcher.

datafetchers/ListingDataFetcher
package com.example.listings.datafetchers;
public class ListingDataFetcher {
}

Our empty class is ready for some methods to do actual data-fetching, but we first need to denote it with a special annotation that tells DGS to include it when assembling the pieces of our API.

This annotation is called @DgsComponent, and all we need to do is import it from our DGS package and stick it on top of our class definition. Now it's officially a member of the DGS team!

package com.example.listings.datafetchers;
import com.netflix.graphql.dgs.DgsComponent;
@DgsComponent
public class ListingDataFetcher {
}

Next, let's give this class a basic method called featuredListings, to match the Query in our schema.

public void featuredListings() {
// specific featuredListings-fetching logic goes here
}

To work as a datafetcher, a method needs to specify which it's responsible for. To clarify this, we have additional DGS annotations that do most of the heavy lifting for us.

Because the featuredListings method is responsible for the featuredListings on the Query type, we can use the @DgsQuery annotation. (Don't forget to import it from our DGS package!)

package com.example.listings.datafetchers;
import com.netflix.graphql.dgs.DgsComponent;
import com.netflix.graphql.dgs.DgsQuery;
@DgsComponent
public class ListingDataFetcher {
@DgsQuery
public void featuredListings() {
// specific featuredListings-fetching logic goes here
}
}

For @DgsQuery to work out of the box, we need to make sure that our method has the SAME name as the Query it resolves. This gives DGS all the info it needs to mark the ListingDataFetcher.featuredListings as the ✨official✨ datafetcher for the Query.featuredListings schema .

There's still one big problem: our featuredListings method isn't actually returning anything. But in our schema, we said that a for the featuredListings should return a list of Listing types!

type Query {
"A curated array of listings to feature on the homepage"
featuredListings: [Listing!]!
}

We need to tweak our method so that the featuredListings actually returns the type of data we said it would. But outside of our schema.graphqls file, where do we actually get these type definitions for our datafetchers to use?

Code generation

We know the properties our Listing type needs, so we could define a corresponding Listing class to represent each object in Java. This approach lets us set all of the properties, including getter and setter methods, while maintaining fine-tuned control over any transformations we might need to do when the data is en route to our clients.

Another option is to use DGS' Code Generation plugin. This tool reads in our schema file and generates classes from the types we've written. With this approach, on a GraphQL typeβ€”like a Listing's title or descriptionβ€”become gettable and settable properties on the corresponding class.

There are benefits to each approach, so we'll steal a bit from both. We're going to use code generation as a starting point, and attach custom logic to our classes as we go along. Let's get started!

Generating our basic classes

Open up your project's build.gradle file and check out plugins at the top. The package that enables code generation in DGS, dgs.codegen, is already listed here.

build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.5'
id 'io.spring.dependency-management' version '1.1.4'
id 'com.netflix.dgs.codegen' version '6.0.3'
}

The plugin runs automatically as part of our code's build process, and it actually takes effect as soon as we run our code.

Start up your server again, either by using your IDE's run button or running the following command in the terminal:

./gradlew bootRun
Task!

In the build.generated.sources folder we'll find a few new packages, including dgs-codegen and dgs-codegen-generated-examples.

πŸ“‚ build
┣ πŸ“‚ classes
┣ πŸ“‚ generated
┃ ┣ πŸ“‚ sources
┃ ┃ ┣ πŸ“‚ annotationProcessor
┃ ┃ ┣ πŸ“‚ dgs-codegen
┃ ┃ ┣ πŸ“‚ dgs-codegen-generated-examples
┃ ┃ ┣ πŸ“‚ headers
┣ πŸ“‚ resources
β”— πŸ“‚ tmp

The dgs-codegen package contains all of the Java types the plugin generated from reading our schema.graphqls file. If we drill down into the dgs-codegen.com.example.listings.generated.types file, we'll see the generated Listing file.

πŸ“‚ dgs-codegen
┣ πŸ“‚ com.example.listings.generated
┃ ┣ πŸ“‚ types
┃ ┃ ┣ πŸ“„ Listing

A closer look at the generated Listing class reveals the collection of properties, getters, and setters that make it possible to create objects that match the specification of the Listing type in our schema. We'll also find a number of other methods that the plugin has added for us to make working with the class and building new instances even easier.

We're going to build on top of this generated Listing class, and extend it with custom functionality in a child class.

Let's create a place in our code for this child class to live. Back in java/com.example.listings, we'll define a new package called models to sit next to datafetchers. This directory will hold the classes we'll use when working with data from our .

πŸ“‚ main
┣ πŸ“‚ java
┃ ┣ πŸ“‚ com.example.listings
┃ ┃ ┃ ┣ πŸ“‚ datafetchers
┃ ┃ ┃ ┣ πŸ“‚ models
┃ ┃ ┃ ┣ πŸ“„ ListingsApplication
┃ ┃ ┃ ┣ πŸ“„ WebConfiguration

Next, we'll define a new class called ListingModel. This class will extend the generated Listing class. Here's what it looks like:

models/ListingModel
package com.example.listings.models;
import com.example.listings.generated.types.Listing;
public class ListingModel extends Listing {
// custom logic will live here
}

We can put this ListingModel class to work right awayβ€”back in our featuredListings datafetcher.

Returning data

Returning now to our datafetchers/ListingDataFetcher file, we'll import the models/ListingModel class, along with the Java List utility.

datafetchers/ListingDataFetcher
import com.example.listings.models.ListingModel;
import java.util.List;

Right away, we can update the return type for our method to be a List of ListingModel types.

@DgsQuery
public List<ListingModel> featuredListings() {
}

First, let's create some new ListingModel instances from our freshly-generated class.

@DgsQuery
public List<ListingModel> featuredListings() {
ListingModel meteorListing = new ListingModel();
}

We need to give our ListingModel instance some attributes, such as id, title, and costPerNight; we can chain those on just by calling the property setter methods. (Make up whatever values you'd like for these , just remember to match the types that we gave them in our schema!)

ListingModel meteorListing = new ListingModel();
meteorListing.setId("1");
meteorListing.setTitle("Beach house on the edge of the Laertes meteor");
meteorListing.setCostPerNight(360.00);
meteorListing.setClosedForBookings(false);
meteorListing.setNumOfBeds(3);

Great! Here's a new ListingModel instance, with all its properties ready to go.

To make our List a bit more robust, we'll add at least one more ListingModel instance.

@DgsQuery
public List<ListingModel> featuredListings() {
ListingModel meteorListing = new ListingModel();
meteorListing.setId("1");
meteorListing.setTitle("Beach house on the edge of the Laertes meteor");
meteorListing.setCostPerNight(360.00);
meteorListing.setClosedForBookings(false);
meteorListing.setNumOfBeds(3);
ListingModel gasGiantListing = new ListingModel();
gasGiantListing.setId("2");
gasGiantListing.setTitle("Unforgettable atmosphere, unbeatable heat, tasteful furnishings");
gasGiantListing.setCostPerNight(124.00);
gasGiantListing.setClosedForBookings(true);
gasGiantListing.setNumOfBeds(4);
return List.of(meteorListing, gasGiantListing);
}

And with that, we've got our very first datafetcher set up, ready to be queried. Here's how your entire datafetcher file should look when you're done:

Practice

What is the primary purpose of a datafetcher?

Key takeaways

  • We use datafetcher methods to return data when a particular schema is queried.
  • The DGS code generation plugin gives us a helpful starting point for the Java classes that map to our types.

Up next

Whew! In the next lesson, we'll see how all the pieces come togetherβ€”by sending our first queries!

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.