GraphQL SPQR 扩展输入对象的变异参数
Posted
技术标签:
【中文标题】GraphQL SPQR 扩展输入对象的变异参数【英文标题】:GraphQL SPQR extend mutation parameters of an input object 【发布时间】:2019-01-23 04:26:00 【问题描述】:能够扩展现有类型非常棒,因为它允许 代码模块化和权限分离。我找到了关于如何在查询中扩展对象输出的好例子(见下文),但没有很好的方法来扩展给定对象的输入。
为了这个例子,假设我们有一个类User
。
class User
String firstName;
String lastName;
如果我们声明一个 bean,我们可以有这样的查询:
/**
* This is valid and can be invoked using
* query
* user(id=1)
* firstName
* lastName
*
*
*/
@GraphQLQuery(name = "user")
public User getUser(@GraphQLArgument(name = "id") long id)
然后在另一个 bean bean 中我们可以扩展 User
/**
* <<this currently works>>
* So now this query becomes valid
* query
* user(id=1)
* firstName
* lastName
* address <-- this is not a top level, but extends User
* streetNam
*
*
*
*/
@GraphQLQuery(name = "address")
public Address getUserAddress(@GraphQLContext User)
同样对于变异,我们可以定义:
/**
* <<this currently works>>
* This can be invoked using:
* mutation
* addUser(user :
* firstName: "John"
* lastName: "Smith"
* )
* fistName
*
*/
@GraphQLMutation(name = "addUser")
public User addUser(@GraphQLArgument(name = "user") User user)
现在我尝试添加address
,以与我们添加它进行查询相同的方式添加,但添加为User
的输入参数。
以下内容仍然在某些 bean 中声明。
/**
* << this is what I am trying to achieve>>
* I want to be able to invoke the following query and not having to declare 'Address' inside of 'User' class.
* mutation
* addUser(user :
* firstName: "John"
* lastName: "Smith"
* address: <-- being able to pass address as argument now, and be part of user.
* streetName: "1 str"
*
* )
* fistName
*
*/
// e.g. something like ...
@GraphQLInputField(name = "address")
public void addAddressToUser(@GraphQLContext User user, @GraphQLArgument Address address)
【问题讨论】:
【参考方案1】:我想出了一种方法,您目前可以做到这一点,但这需要一些努力。 我正在使用 GraphQL-SPQR 0.9.8(我将在几天内发布)。您可以在 0.9.7 中实现相同的效果,只是不太符合人体工程学。
@Test
public void testSchema()
GraphQLSchema schema = new GraphQLSchemaGenerator()
// Ignore extra fields (like "address") when deserializing
.withValueMapperFactory(JacksonValueMapperFactory.builder()
.withConfigurers(conf -> conf.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false))
.build())
.withInputFieldBuilders((conf, defaults) -> defaults.prepend(new UserInputBuilder(defaults.getFirstOfType(JacksonValueMapper.class))))
.withArgumentInjectors(new UserInjector())
.withOperationsFromSingleton(new TestService())
.generate();
GraphQL exe = GraphQL.newGraphQL(schema).build();
ExecutionResult result = exe.execute("user(in: name: \"A. Man\", address: type: \"home\", street: name: \"Fakestreet\", number: 123) name, street number");
public class TestService
@GraphQLQuery //or mutation, no difference
public User user(User in)
return in;
// Redefines how User objects are deserizalized
public static class UserInjector extends InputValueDeserializer
@Override
public Object getArgumentValue(ArgumentInjectorParams params)
User user = (User) super.getArgumentValue(params);
Map<?, ?> rawInput = (Map<?, ?>) params.getInput();
Address address = params.getResolutionEnvironment().valueMapper.fromInput(rawInput.get("address"), GenericTypeReflector.annotate(Address.class));
// Preprocess the address in any way you need, here I just extract the street
user.setStreet(address.getStreet());
return user;
@Override
public boolean supports(AnnotatedType type)
return GenericTypeReflector.isSuperType(User.class, type.getType());
//Redefines the way User input type is mapped
public static class UserInputBuilder implements InputFieldBuilder
private final InputFieldBuilder original;
public UserInputBuilder(InputFieldBuilder original)
this.original = original;
@Override
public Set<InputField> getInputFields(InputFieldBuilderParams params)
Set<InputField> fields = original.getInputFields(params);
// Add the extra "address" field you want
fields.add(new InputField("address", "User's home address", GenericTypeReflector.annotate(Address.class), null, null));
return fields;
@Override
public boolean supports(AnnotatedType type)
return GenericTypeReflector.isSuperType(User.class, type.getType());
public class User
private String name;
private Street street;
public User(String name, Street street)
this.name = name;
this.street = street;
public String getName()
return name;
public Street getStreet()
return street;
@GraphQLIgnore //can be filtered out in a different way, without touching this class
public void setStreet(Street street)
this.street = street;
public class Address
private Street street;
private String type;
public Address(Street street, String type)
this.street = street;
this.type = type;
public Street getStreet()
return street;
public String getType()
return type;
也就是说,在 Jackson(或 Gson,无论您使用什么)中注册自定义反序列化器并跳过自定义 ArgumentInjector
可能会更容易。
【讨论】:
0.10.0 在这方面有什么变化吗?有你知道的计划吗? @Ernio 没有任何改变,因为我发现用例相当模糊,并且在这种情况下对添加新语法持谨慎态度。我建议您考虑使用中继兼容模式 (generator.withRelayCompliantMutations()
),该模式将为每个突变生成特定的输入类型。这似乎是一种潜在的更清洁的方法,因为它不会以难以理解和调试的方式扭曲类型映射。与附加字段由方法支持的查询不同,示例中的@InputField
方法实际上并没有做 任何事情。这听起来不是一个好的设计。以上是关于GraphQL SPQR 扩展输入对象的变异参数的主要内容,如果未能解决你的问题,请参考以下文章
带有 Spring Boot 和事务边界的 graphql-spqr
具有弹簧安全性的 JWT 身份验证 graphql-spqr