我可以安全地从 GraphQLResolver 回调 GraphQLQueryResolver 吗?

Can I safely call back a GraphQLQueryResolver from a GraphQLResolver?

我的问题是:实施 解析 方法时什么是最好的?直接调用数据存储库或回调 主解析器 也就是实现 GraphQLQueryResolver 的解析器(前提是它有适当的方法)?换句话说(参见下面的示例),在回调 主解析器 DataFetchingEnvironment 是否正确 adjusted/set?

注意:如果您不熟悉 Resolvers 如何使用 GraphQL Java 工具,我会让您看一下 @ https://www.graphql-java-kickstart.com/tools/schema-definition/

举个例子。

在 Spring 启动应用程序中,使用 GraphQL Java 工具(具有 graphql-spring-boot-starter 依赖项),让我们有这个架构:

type User {
  id: ID
  name: String
  company: Company
}

type Company {
  id: ID
  name: String
}

具有匹配的 POJO 或实体(省略 getters/setters):

class User {

  private Long id;
  private String name;
  private Long idCompany;

}

class Company {

  private Long id;
  private String name;

}

和这些 解析器(注意:UserRepository 和 CompanyRepository 是您常用的 DAO/Repository-kind-of-classes,由 Spring Data (JPA)、其他东西或您的自己的自定义实现,无论...):

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());
  }

}

在每个方法的末尾添加 DataFetchingEnvironment environment 并在执行对各种(数据)存储库的调用之前使用它时,这(更)有意义。

继续上面的例子,这样做是否正确(即当再次传输到主 QueryResolver 时 DataFetchingEnvironment 是否会被正确填充)?

UserResolver implements GraphQLResolver<User> {

  @Autowired
  private QueryResolver queryResolver;

  public Company company(User user, DataFetchingEnvironment environment) {
    return queryResolver.company(user.getIdCompany(), environment);
  }

}

简答

您可以将您的解析器调用委托给服务层,但不要在 resolvers/services 之间传递 DataFectingEnvironment。它不会被正确填充。

长答案

它不安全,可能会导致难以查明的错误和数据丢失。

DataFetchingEnvironment 由正在执行的 graphql query/mutation 填充,您希望解析器方法中的 DataFetchingEnvironment 与正在调用的解析器方法一致。

考虑以下架构:

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
}

示例 1:查询

query {
  searchRRatedMovie(name: "NotRRatedMovie") {
    title
  }
}

电影“NotRRatedMovie”不是 R 级,我们可以预期此查询 return 一个空数据。

现在,下面的实现将 DataFetchingEnvironment 从 searchRRatedMovie 传递到 searchMovie 查询解析器实现。

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);
    }
  }

}

看起来不错,但查询不会 return null。

第一个解析器将调用 searchRRatedMovie("NotRRatedMovie", environment)。环境不包含 "rating" 参数。到达行时:if(!environment.containsArgument("rating")) { "rating" 参数不存在,它将进入 if 语句,returning repository.findByTitle("NotRRatedMovie") 而不是预期的 repository.findByTitleAndRating("NotRRatedMovie","R").

示例 2:具有部分更新的突变

我们可以使用 DataFetchingEnvironment 参数在突变中实现部分更新:如果参数是 null 我们需要 DataFetchingEnvironment 参数来告诉我们参数是否是 null 因为它被设置为null(即突变应该将基础值更新为 null)或者因为它根本没有设置(即突变不应该更新基础值)。

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;
  }

}

此处 updateActor 解析器需要一个输入参数(匹配 updateActor 变异定义)。因为我们传递了错误填充的环境,所以实现失败了。

解决方案

没有 DataFetchinEnvironment 的部分更新

如果你想实现部分更新,你可以不使用 DataFectingEnvironment 来实现,就像我在这条评论中所做的那样:https://github.com/graphql-java-kickstart/graphql-java-tools/issues/141#issuecomment-560938020

在将 DataFetchingEnvironment 传递给下一个解析器之前重建 DataFetchingEnvironment

如果您确实需要DataFetchingEnvironment,您仍然可以构建一个新的以传递给下一个解析器。这可能会更加困难和容易出错,但您可以查看原始 DataFetchingEnvironment 是如何在 ExecutionStrategy.java https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/execution/ExecutionStrategy.java#L246

中创建的