我可以安全地从 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
中创建的
我的问题是:实施 解析 方法时什么是最好的?直接调用数据存储库或回调 主解析器 也就是实现 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
中创建的