0

我的问题是:实施解析方法时最好的方法是什么?直接调用数据存储库或回调一个主解析器,也就是一个实现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);
  }

}
4

1 回答 1

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 级的,我们可以期望这个查询返回一个空数据。

现在,下面的实现将来自 searchRRatedMovie 的 DataFetchingEnvironment 传递给 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

于 2020-09-25T04:44:56.767 回答