Kann ich einen GraphQLQueryResolver sicher von einem GraphQLResolver zurückrufen?
Meine Frage ist: Was ist das Beste bei der Implementierung von Auflösungsmethoden ? direkter Aufruf von Datenrepositorys oder Rückruf eines Hauptresolvers oder einer Implementierung GraphQLQueryResolver(vorausgesetzt, er verfügt über geeignete Methoden)? Mit anderen Worten (siehe Beispiel unten): DataFetchingEnvironmentWird der Wert beim Zurückrufen eines Hauptauflösers richtig eingestellt / eingestellt ?
Hinweis: Wenn Sie nicht mit der ResolversArbeit mit GraphQL Java Tools vertraut sind , lassen Sie sich einfach einen Blick auf @ werfenhttps://www.graphql-java-kickstart.com/tools/schema-definition/
Nun das Beispiel.
In einer Spring Boot-App mit GraphQL Java Tools (mit der graphql-spring-boot-starterAbhängigkeit) haben wir dieses Schema:
type User {
id: ID
name: String
company: Company
}
type Company {
id: ID
name: String
}
mit passendem POJO oder Entität (Getter / Setter werden weggelassen):
class User {
private Long id;
private String name;
private Long idCompany;
}
class Company {
private Long id;
private String name;
}
und diese Resolver (Hinweis: UserRepository und CompanyRepository sind Ihre üblichen DAO / Repository-Klassen, die entweder von Spring Data (JPA), etwas anderem oder Ihrer eigenen benutzerdefinierten Implementierung unterstützt werden, was auch immer ...):
QueryResolver implements GraphQLQueryResolver {
@Autowired
private UserRepository userRepository;
@Autowired
private CompanyRepository companyRepository;
public User user(String id) {
return userRepository.findById(id);
}
public Company company(String idCompany) {
return companyRepository.findById(idCompany);
}
}
UserResolver implements GraphQLResolver<User> {
@Autowired
private CompanyRepository companyRepository;
public Company company(User user) {
return companyRepository.findById(user.getIdCompany());
}
// ...or should I do:
@Autowired
private QueryResolver queryResolver;
public Company company(User user) {
return queryResolver.company(user.getIdCompany());
}
}
Dies ist (sinnvoller), wenn Sie DataFetchingEnvironment environmentam Ende jeder Methode hinzufügen UND sie verwenden, bevor Sie die Aufrufe an die verschiedenen (Daten-) Repositorys ausführen.
Wenn Sie mit dem obigen Beispiel DataFetchingEnvironmentfortfahren , wäre es richtig, dies zu tun (dh wäre das ordnungsgemäß ausgefüllt, wenn es erneut an den Haupt-QueryResolver übertragen wird)?
UserResolver implements GraphQLResolver<User> {
@Autowired
private QueryResolver queryResolver;
public Company company(User user, DataFetchingEnvironment environment) {
return queryResolver.company(user.getIdCompany(), environment);
}
}
Antworten
Kurze Antwort
Sie können Ihre Resolver-Aufrufe an die Service-Schicht delegieren, die DataFecthingEnvironment jedoch nicht zwischen Resolvern / Services übergeben. Es wäre nicht richtig ausgefüllt.
Lange Antwort
Es ist nicht sicher und kann zu schwer zu lokalisierenden Fehlern und Datenverlusten führen.
Die DataFetchingEnvironment wird aus der ausgeführten graphql-Abfrage / Mutation ausgefüllt, und Sie würden erwarten, dass die DataFetchingEnvironment in Ihrer Resolver-Methode mit der aufgerufenen Resolver-Methode übereinstimmt.
Betrachten Sie das folgende Schema:
type Movie {
id: ID!
title: String!
rating: String
actors: [Actor]
}
type Actor {
id: ID!
name: String!
role: String
}
input ActorUpdateInput {
id: ID!
name: String
role: String
}
type Query {
#Search movies with a specified Rating
searchMovie(name: movieTitle, rating: String): Book
#Search R-rated movies
searchRRatedMovie(name: movieTitle): Book
}
type Mutation {
#Update a movie and its actors
updateMovie(id:Id!, title: String, actors: [ActorUpdateInput]): Movie
#Update an actor
updateActor(input: ActorUpdateInput!): Actor
}
Beispiel 1: Abfrage
query {
searchRRatedMovie(name: "NotRRatedMovie") {
title
}
}
Der Film "NotRRatedMovie" hat kein R-Rating. Wir können davon ausgehen, dass diese Abfrage Nulldaten zurückgibt.
Die folgende Implementierung übergibt nun die DataFetchingEnvironment von searchRRatedMovie an die searchMovie-Abfrage-Resolver-Implementierung.
public class QueryResolver {
@Autowired
MovieRepository repository;
public Movie searchRRatedMovie(String title, DataFetchingEnvironment environment) {
return this.searchMovie(name, "R", environment);
}
public Movie searchMovie(String title, String rating, DataFetchingEnvironment environment) {
if(!environment.containsArgument("rating")) {
//if the rating argument was omitted from the query
return repository.findByTitle(title);
} else if(rating == null) {
//rating is an argument but was set to null (ie. the user wants to retrieve all the movies without any rating)
return repository.findByTitleAndRating(title, null);
} else {
repository.findByNameAndTitle(name,rating);
}
}
}
Das sieht gut aus, aber die Abfrage gibt nicht null zurück.
Der erste Resolver ruft an searchRRatedMovie("NotRRatedMovie", environment). Die Umgebung enthält kein "rating"Argument. Beim Erreichen der Zeile: if(!environment.containsArgument("rating")) {Das "rating"Argument ist nicht vorhanden und gibt die if-Anweisung ein, wobei repository.findByTitle("NotRRatedMovie")anstelle der erwarteten zurückgegeben wird repository.findByTitleAndRating("NotRRatedMovie","R").
Beispiel 2: Mutation mit Teilaktualisierungen
Wir können die DataFetchingEnvironment-Argumente verwenden, um Teilaktualisierungen in einer Mutation zu implementieren: Wenn ein Argument vorhanden ist null, benötigen wir die DataFetchingEnvironment-Argumente, um uns mitzuteilen, ob das Argument auf nullfestgelegt wurde null(dh die Mutation sollte den zugrunde liegenden Wert auf aktualisieren null) oder darauf wurde überhaupt nicht gesetzt (dh die Mutation sollte den zugrunde liegenden Wert nicht aktualisieren).
public class MutationResolver {
@Autowired
MovieRepository movieRepository;
@Autowired
ActorRepository actorRepository;
public Movie updateMovie(Long id, String title, List<ActorUpdateInput> actors, DataFetchingEnvironment environment) {
Movie movie = movieRepository.findById(id);
//Update the title if the "title" argument is set
if(environment.containsArgument("title")) {
movie.setTitle(title);
}
if(environment.containsArgument("actors")) {
for(ActorUpdateInput actorUpdateInput : actors) {
//The passing the environment happens here
this.updateActor(actorUpdateInput, environment);
}
}
return movie;
}
public Actor updateActor(ActorUpdateInput input, DataFetchingEnvironment environment) {
Actor actor = actorRepository.findById(input.getId());
//We retrieve the argument "input". It is a Map<String, Object> where keys are arguments of the ActorUpdateInput
Map<String, Object> actorArguments = (Map<String, Object>) env.getArguments().get("input");
//Problem: if the environment was passed from updateMovie, it does not contains an "input" parameter! actorArguments is now null and the following code will fail
//Update the actor name if the "name" argument is set
if (actorArguments.containsKey("name")) {
actor.setName(input.getName());
}
//Update the actor role if the "role" argument is set
if (actorArguments.containsKey("role")) {
actor.setRole(input.getRole());
}
return actor;
}
}
Hier erwartete der UpdateActor-Resolver ein Eingabeargument (das mit der UpdateActor-Mutationsdefinition übereinstimmen würde). Da wir eine falsch besetzte Umgebung passiert haben, ist die Implementierung unterbrochen.
Lösung
Teilaktualisierungen ohne DataFetchinEnvironment
Wenn Sie Teilaktualisierungen implementieren möchten, können Sie dies tun, ohne die DataFecthingEnvironment zu verwenden, wie ich es in diesem Kommentar getan habe: https://github.com/graphql-java-kickstart/graphql-java-tools/issues/141#issuecomment-560938020
Erstellen Sie die DataFetchingEnvironment neu, bevor Sie sie an den nächsten Resolver übergeben
Wenn Sie die DataFetchingEnvironment wirklich benötigen, können Sie dennoch eine neue erstellen, die an den nächsten Resolver übergeben wird. Dies wird wahrscheinlich schwieriger und fehleranfälliger, aber Sie können sich ansehen, wie die ursprüngliche DataFetchingEnvironment in ExecutionStrategy.java erstellt wirdhttps://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/execution/ExecutionStrategy.java#L246