【问题标题】:Can I safely call back a GraphQLQueryResolver from a GraphQLResolver?我可以从 GraphQLResolver 安全地回调 GraphQLQueryResolver 吗?
【发布时间】:2021-01-10 10:21:55
【问题描述】:

我的问题是:实现 resolving 方法时最好的方法是什么?直接调用数据存储库或回调一个主解析器,也就是一个实现GraphQLQueryResolver(只要它有适当的方法)?换句话说(见下面的例子),DataFetchingEnvironment 在回调主解析器时是否正确调整/设置?

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

现在是例子。

在 Spring Boot 应用程序中,使用 GraphQL Java 工具(具有 graphql-spring-boot-starter 依赖项),让我们拥有以下架构:

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

type Company {
  id: ID
  name: String
}

具有匹配的 POJO 或实体(getter/setter 被省略):

class User {

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

}

class Company {

  private Long id;
  private String name;

}

以及这些解析器(注意:UserRepository 和 CompanyRepository 是您常用的 DAO/Repository 类的类,由 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 并在执行对各种(数据)存储库的调用之前使用它时(更)有意义。

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

UserResolver implements GraphQLResolver<User> {

  @Autowired
  private QueryResolver queryResolver;

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

}

【问题讨论】:

  • 据我所知应该没问题,因为你正在通过它

标签: spring-boot graphql graphql-java graphql-java-tools


【解决方案1】:

简答

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

长答案

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

DataFetchingEnvironment 是从正在执行的 graphql 查询/变异中填充的,您希望解析器方法中的 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,我们可以预期此查询返回空数据。

现在,下面的实现将 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);
    }
  }

}

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

第一个解析器将调用searchRRatedMovie("NotRRatedMovie", environment)。环境不包含 "rating" 参数。到达行时:if(!environment.containsArgument("rating")) { "rating" 参数不存在,它将进入 if 语句,返回 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 的部分更新

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

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

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

中创建的

【讨论】:

    猜你喜欢
    • 2014-10-20
    • 1970-01-01
    • 2020-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    • 2016-01-25
    • 2020-10-01
    相关资源
    最近更新 更多