【问题标题】:GraphQL SPQR extend mutation parameters of an input objectGraphQL SPQR 扩展输入对象的变异参数
【发布时间】: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) {

    }

【问题讨论】:

    标签: java graphql graphql-java graphql-spqr


    【解决方案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 方法实际上并没有 任何事情。这听起来不是一个好的设计。
    猜你喜欢
    • 2019-01-02
    • 2019-03-09
    • 2017-02-07
    • 2022-01-06
    • 2018-10-07
    • 2019-11-09
    • 2017-03-14
    • 2021-08-11
    • 2019-12-24
    相关资源
    最近更新 更多