8. Contributing fields to an entity
15m

Overview

Even though we've defined the Listing in our reviews and contributed to it, we haven't told it how to resolve these fields.

In this lesson, we will:

  • Create a reference
  • Populate the reviews and overallRating with data
  • Learn about the @DgsEntityFetcher annotation

The reference resolver

When we for a particular listing and its review data, we need a way to tell the reviews which listing we're talking about. Then the reviews will understand what data to provide!

To do exactly this, the passes along the representation we talked about in the last lesson: the minimum information required for the reviews to put together some idea of which listing it's fetching data for.

Example Listing entity representation
{
"__typename": "Listing",
"id": "listing-3"
}

But where in the reviews does this representation actually go?

This is just the piece we're missing: the reference . The reference resolver is the special method that can receive an representation from the , create a new instance of a Listing from that representation data, and return it.

Let's see what this looks like in code, and walk through the process step-by-step.

Adding resolveListingReference

Let's open up our datafetcher class in the reviews , under com.example.reviews/datafetchers/ReviewsDataFetcher.

Right away, let's take care of adding some imports at the top of the file. We'll use these soon.

import java.util.Map;
import com.example.reviews.generated.types.Listing;

Note: Not seeing the Listing type in your generated folder? Try restarting the server!

Make some space down in the body of the class for our new method. To be extra clear about its job, we'll give it the name resolveListingReference. (But you can call this method whatever you want!)

datafetchers/ReviewsDataFetcher
public void resolveListingReference() {
// TODO
}

This method will receive the representation, which looks something like the following snippet: just the __typename and the we set as the 's primary key, id.

{__typename=Listing, id=listing-1}

Let's give our method a parameter to hold this representation. It's considered a Map<String, Object> type, and we'll call it entityRepresentation. Let's print out entityRepresentation.toString() so we can see what this method receives.

public void resolveListingReference(Map<String, Object> entityRepresentation) {
System.out.println(entityRepresentation.toString());
}

The @DgsEntityFetcher annotation

In order for our DGS server to know that this is the method that should receive an from the , we need to mark it with a specific annotation: @DgsEntityFetcher.

This annotation has a property called name, which we'll use to define the name of the that it resolves: Listing!

Let's apply this annotation to our method.

@DgsEntityFetcher(name = "Listing")
public void resolveListingReference(Map<String, Object> entityRepresentation) {
System.out.println(entityRepresentation.toString());
}

Now it's extra clear that when the brings a Listing representation to our reviews , this is the method that we'll use to resolve which listing we need to provide data for!

Go back to your terminal and restart the reviews server. Our rover dev process should still be running on port 4000, so let's return there and try out our again.

query GetListingAndReviews {
listing(id: "listing-1") {
title
description
numOfBeds
amenities {
name
category
}
overallRating
reviews {
id
text
}
}
}

When we run the , we still won't get any review-related data back; but when we check the terminal of our reviews , we'll see that our representation has arrived and is printed out!

{__typename=Listing, id=listing-1}

Our reviews is successfully receiving the listing representation from the . Now, we just need to make sure our method returns a new Listing instance that other datafetcher methods can access and attach data to.

Delete the print statement, and replace it by instantiate a new Listing instance. We can also update our method's return type to Listing.

@DgsEntityFetcher(name = "Listing")
public Listing resolveListingReference(Map<String, Object> entityRepresentation) {
Listing listing = new Listing();
}

Next, let's pull the "id" value from our entityRepresentation. We can cast it as a String type with the following line.

String id = (String) entityRepresentation.get("id");

Lastly, we'll set this as the id on our listing, and return it.

listing.setId(id);
return listing;

Great! We can consider our reference to the listing "resolved". We're ready to provide those review-relevant —reviews and overallRating—with their own datafetcher methods.

Adding Listing.reviews

Let's tackle the reviews method first.

Inside the ReviewsDataFetcher class, and let's define a new method called reviews. This is a regular datafetcher, so we'll use the @DgsData annotation, setting our parentType as Listing.

@DgsData(parentType="Listing")
public void reviews() {}

In our , the Listing.reviews returns a type of [Review!]!. So let's update our method with the corresponding Java return type of Flux<ReviewDto>.

public Flux<ReviewDto> reviews() {}

This method should use the id from the representation to find and return all the relevant reviews in the database. Because this datafetcher resolves a on an entity type, we know that the previous datafetcher in the chain was the resolveListingReference method we just defined. This means we can access its return value (the Listing instance) using the DgsDataFetchingEnvironment parameter.

Let's bring that into our method as a parameter called dfe.

public Flux<ReviewDto> reviews(DgsDataFetchingEnvironment dfe) {}

We can call the getSource method on dfe to get access to the Listing instance; then call the listing's getId method to access its id value.

public Flux<ReviewDto> reviews(DgsDataFetchingEnvironment dfe) {
Listing listing = dfe.getSource();
String id = listing.getId();
}

With our listing id in hand, we can look up all of the reviews in our database that are associated with that listing. We're using our ReviewController, which is already instantiated on our class, to access our in-memory . It provides a mapping for reviewsForListing, so let's call that method here and pass in our id.

return this.reviewController.reviewsForListing(id);

The overallRating method

Onto the overallRating datafetcher method! Let's set up the initial structure with the @DgsData annotation.

@DgsData(parentType="Listing")
public void overallRating() {
// TODO
}

Try this one out on your own, and check out the ReviewController class for a helpful method you can use to retrieve this data. (Hint: Check out its return type for a helpful cue on what our method should return!)

When you're ready, compare your code to our finished method below.

@DgsData(parentType="Listing")
public Mono<Float> overallRating(DgsDataFetchingEnvironment dfe) {
Listing listing = dfe.getSource();
String id = listing.getId();
return this.reviewController.averageRatingForListing(id);
}

Note: Because we're coding in the reactive style, we've used the Flux type so far to describe the stream of data that we return. However, since we're returning just a single value—the average of a listing's ratings, returned as a Float—we can use the Mono type instead and pass it the type of Float.

Running our dream query

Time to recompile!

Task!

With the rover dev process still running, let's try out our at http://localhost:4000.

query GetListingAndReviews {
listing(id: "listing-1") {
title
description
numOfBeds
amenities {
name
category
}
overallRating
reviews {
id
text
}
}
}

Submit the query, and... we've got data! 🎉 We've associated reviews with a listing and made our dream query come to life!

Reviewing the query plan

Let's check out the to see how this data came together. We'll see that first, the will fetch data from listings, and then use that data to build out its request to reviews. The last step is flattening the response from both into a single instance of a Listing type for the listing we've queried!

http://localhost:4000

The operation in Sandbox, with the Query Plan Preview opened, showing a linear path to retrieve data from both subgraphs

Still seeing reviews: null? Try restarting your DGS server! The rover dev process running our on port 4000 will automatically refresh.

Practice

Where should an entity's reference resolver method be defined?

Key takeaways

  • Any that contributes to an needs to define a reference method for that entity. This method is called whenever the needs to access fields of the entity from within another subgraph.
  • In DGS, we denote a method as a reference using the @DgsEntityFetcher annotation, passing in the name of the type it resolves. This method receives an representation from the .
  • An representation is an object that the uses to represent a specific instance of an entity. It includes the entity's type and its key (s).

Up next

Awesome! Our listings and reviews services are now collaborating on the same Listing . Each contributes its own , and the by our local rover dev process packages up the response for us. Emphasis on the word local; to get our changes actually "live" (at least in the tutorial sense of the word), we need to tell about them!

In the next lesson, we'll take a look at how we can land these changes safely and confidently using and .

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.