Graphql - Java 실천 (1) - 조회

16907 단어 Graphql-X
서버 에 있어 핵심 은 Schema 를 정의 하고 대외 적 으로 제공 하 는 APIGraphQL - Java 로 조합 하여 Schema 를 정의 하 는 두 가지 방법 을 제공 하 는 것 이다.
  • Java 코드 정의
  • GraphQL SDL (IDL) 정의
  • Java
    단순 조회
    GraphQL - Java 는 자바 8 지원 이 필요 합 니 다. Lambda 표현 식 을 자주 사용 합 니 다. 여기 서 직접 자바 의 예 를 들 어 Maven 의존:
     
    
        com.graphql-java
        graphql-java
        8.0
    
    

     
    많은 방법 과 가방 이 static import 입 니 다. 주의 하 세 요. 그렇지 않 으 면 모두 잘못 보고 합 니 다.
     
    import static graphql.Scalars.GraphQLString;
    import static graphql.schema.AsyncDataFetcher.async;
    import static graphql.schema.GraphQLArgument.newArgument;
    import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
    import static graphql.schema.GraphQLObjectType.newObject;
    import static graphql.schema.idl.RuntimeWiring.newRuntimeWiring;
    

     
    Pojo:
     
    public class User {
        private int id;
        private int age;
        private String userName;
        private List dogs;
    }
    public class Dog {
        private int id;
        private String dogName;
    }
    

     
    우선 사용자 와 Dog 를 기반 으로 2 개의 사용자 정의 대상 유형 을 정의 합 니 다. Dog:
     
    public static GraphQLObjectType getDogType(){
        GraphQLObjectType dogType = newObject()
                .name("dog")
                .field(newFieldDefinition().name("id").type(Scalars.GraphQLInt).build())
                .field(newFieldDefinition().name("dogName").type(GraphQLString).build())
                .build();
        return dogType;
    }
    

     
    Person:
     
    //user type
        public static GraphQLObjectType getUserType(){
            GraphQLObjectType userType = newObject()
                    .name("user")
                    .field(newFieldDefinition().name("id").type(Scalars.GraphQLInt)).build())
                    .field(newFieldDefinition().name("age").type(Scalars.GraphQLInt).build())
                    .field(newFieldDefinition().name("userName").type(GraphQLString).build())
                    .field(newFieldDefinition().name("dogs").type(new GraphQLList(getDogType())).build())
                    .build();
            return userType;
        }
    

     
    모든 Field 에 대응 하 는 유형 을 정의 해 야 하 며 기본적으로 GraphQL 구축 대상 은 사용 하 는 작성 자 모드 입 니 다.GraphQL 의 대상 유형 GraphQLobject Type 도 클 라 이언 트 에 노출 된 API 가 될 수 있 으 며 사용자 정의 형식 만 사용 할 수 있 는 것 이 아 닙 니 다.Query API:
     
    //query
        public static GraphQLObjectType userQuery(){
        //getUser 返回构造的user对象
        //new GraphQLList 代表返回的是List 为了测试,这里只放一个
            return newObject()
                    .name("userQuery")
                    .field(newFieldDefinition().type(new GraphQLList(getUserType())).name("user").staticValue(getUsers()))
                    .build();
        }
    
        //schema
        public static void mainExec() throws InterruptedException {
            //创建Schema
            GraphQLSchema schema = GraphQLSchema.newSchema()
                    .query(userQuery())
                    .build();
            //测试输出
            GraphQL graphQL = GraphQL.newGraphQL(schema).build();
            Map result = graphQL.execute("query userQuery{user{id,age,dogs}}").getData();
            System.out.println(result);
    }
    

     
    Console: {user=[{id=1, age=4, dogs=[{id=100, dogName=Dog19fbbb43-db17-4ca8-a515-7ee8a28c92a9}]}]}
    매개 변수 조회
    다음은 검색 매개 변수 에 따라 결 과 를 얻 는 방법 을 살 펴 보 자.
    필드 마다 DataFetcher 가 있 습 니 다. 필드 에 DataFetcher 를 지정 하지 않 으 면 기본 Property DataFetcher 를 자동 으로 사용 합 니 다. Property DataFetcher 는 Map 과 자바 Beans 에서 데 이 터 를 가 져 옵 니 다. 따라서 Schema 의 field 이름 은 Map 의 key 값 이나 Source Object 의 자바 bean 필드 이름과 동시에 field 에 DataFetcher 를 지정 할 필요 가 없습니다.대상 TypeResolver 는 graphql - java 가 데이터 의 실제 유형 (type) 을 판단 하 는 데 도움 을 줍 니 다. 따라서 Interface 와 Union 은 관련 된 TypeResolver (형식 식별 기) 를 지정 해 야 합 니 다. 위의 예 를 바탕 으로 ID 가 1 인 User 를 조회 하려 면 코드 가 다음 과 같이 변경 되 어야 합 니 다. 모든 Argument 는 Field 에 만 정의 할 수 있 습 니 다.
     
    //创建查询Field
        public static GraphQLFieldDefinition createUserField(){
            return newFieldDefinition().name("user").argument(newArgument().name("id").type(Scalars.GraphQLInt).build()).
                    //返回类型
                    type(new GraphQLList(getUserType())).
                    dataFetcher(environment -> {
                        //上一级对象数据 environment.getSource()
                        //Map environment.getArguments()
                        //环境上下文,整个查询冒泡中都可以使用 environment.getContext()
                        Integer id = environment.getArgument("id");
                        System.out.println("argument:id="+id);
                        // repository 处理
                        return getUsers(id);
                    }).
                    build();
        }
    
        //query
        public static GraphQLObjectType userQuery(){
            return newObject()
                    .name("userQuery")
                    .field(createUserField())
                    .build();
        }
    
        //schema
        public static void mainExec() throws InterruptedException {
            //创建Schema
            GraphQLSchema schema = GraphQLSchema.newSchema()
                    .query(userQuery())
                    .build();
            //测试输出
            GraphQL graphQL = GraphQL.newGraphQL(schema).build();
            Map result = graphQL.execute("query userQuery{user(id:1){id,age,dogs{id,dogName}}}").getData();
            System.out.println(result);
        }
    

     
    Console: argument: id = 1 {user = [{id = 1, age = 6, dogs = [{id = 100, dogName = Dog14afa69d - 42ce - 49e9 - ae48 - 9873 ef68ece 1}]}} Schema 에 파 라 메 터 를 추가 한 후 사용 할 때 파 라 메 터 를 가지 고 있 지 않 아 도 됩 니 다. 동적 매개 변수 Variable 을 사용 하려 면 다음 과 같이 변경 하면 됩 니 다.
     
        Map variable = Maps.newHashMap();
        variable.put("iidd",1);
        ExecutionInput executionInput = ExecutionInput.newExecutionInput().variables(variable).query("query userQuery($iidd:Int){user(id:$iidd){id,age,dogs{id,dogName}}}").build();
    Map result = graphQL.execute(executionInput).getData();
    

     
    ExecutionInput 은 표준 GrapQL - HTTP 조회 요청 체 와 Input 실행 을 구성 할 수 있 으 며 내부 구성원 을 보면 한눈 에 알 수 있 습 니 다.
     
    private final String query;
    private final String operationName;
    private final Object context;
    private final Object root;
    private final Map variables;
    

     
    IDL
    IDL 과 자바 가 실현 하 는 차이 점 은 바로 접 두 사 를 graphqls 로 정의 해 야 하 는 schema 파일 입 니 다. 그 중에서 자바 정의 의 유형, 파라미터 등 을 포함 하고 있 습 니 다. Schema 에 최종 적 으로 노출 된 API userQuery 든 사용자 정의 형식 User 와 Dog 이 든 모두 GraphQL 의 일치 하 는 유형 구 조 를 유지 하고 있 음 을 알 수 있 습 니 다. [] 대표 배열 변수 이름: 매개 변수 유형 :
     
    schema {
        query: userQuery
    }
    type userQuery {
        user(id: Int): [User]
    }
    type User {
        id: Int
        age: Int
        userName: String!
        dogs: [Dog]
    }
    type Dog {
        id: Int
        dogName: String!
    }
    

     
    IDL 에서 정의 하 는 Type 은 프로젝트 의 같은 이름 의 클래스 와 자동 으로 연결 되 며 필드 도 마찬가지 입 니 다.이에 따라 비 스칼라 형식의 필드 는 DataFetcher, TypeResolvers 등 을 포함 하여 자체 적 으로 연결 해 야 합 니 다.사용자 정의 바 인 딩 이 없 으 면 기본 Property DataFetcher 를 사용 하여 현재 대상 인 스 턴 스 의 getXXX 방법 을 자동 으로 호출 합 니 다. 예 를 들 어 user 내부 의 dogs 는 사용자 정의 되 지 않 았 기 때문에 user 인 스 턴 스 를 사용 한 getDogs 방법의 반환 값 으로 부 여 됩 니 다.
     
    //加载schema文件
        private static File loadSchema(final String s) {
            System.out.println(GraphqlTest2.class.getClassLoader().getResource("graphql/userDemo.graphqls"));
            return new File(GraphqlTest2.class.getClassLoader().getResource(s).getFile());
        }
        //构建一个运行时Java语义 绑定schema,包括(datafetcher、typeResolver、customScalar)
        private static RuntimeWiring buildRuntimeWiring() {
            //return RuntimeWiring.newRuntimeWiring().wiringFactory(new EchoingWiringFactory()).build();
            return RuntimeWiring.newRuntimeWiring()
                    // this uses builder function lambda syntax
                    .type("userQuery", typeWiring -> typeWiring
                                    .dataFetcher("user", environment -> {
                                        //上一级对象数据 environment.getSource()
                                        //Map environment.getArguments()
                                        //环境上下文,整个查询冒泡中都可以使用 environment.getContext()
                                        Integer id = environment.getArgument("id");
                                        System.out.println("argument:id=" + id);
                                        // repository 处理
                                        return getUsers(id);
                                    })
                    ).build();
        }
    

     
    마지막 으로 정적 Schema 와 바 인 딩 (wiring) 을 통합 하여 실행 가능 한 Schema 를 만 들 수 있 습 니 다. 최종 결 과 는 자바 코드 가 정의 하 는 schema 운행 과 일치 합 니 다.
     
    SchemaParser schemaParser = new SchemaParser();
    SchemaGenerator schemaGenerator = new SchemaGenerator();
    
    File schemaFile = loadSchema("graphql/userDemo.graphqls");
    
    TypeDefinitionRegistry typeRegistry = schemaParser.parse(schemaFile);
    RuntimeWiring wiring = buildRuntimeWiring();
    GraphQLSchema schema = schemaGenerator.makeExecutableSchema(typeRegistry, wiring);
    //测试输出
    GraphQL graphQL = GraphQL.newGraphQL(schema).build();
    Map variable = Maps.newHashMap();
    variable.put("iidd",1);
    ExecutionInput executionInput = ExecutionInput.newExecutionInput().variables(variable).
            query("query userQuery($iidd:Int){user(id:$iidd){id,age,dogs{id,dogName}}}").build();
    Map result = graphQL.execute(executionInput).getData();
    System.out.println(result);
    

     
    매개 변 수 를 주 는 것 은 배열 [] 의 예 입 니 다. 약간의 차이 만 있 습 니 다.
     
    type userQuery {
        user(id: [Int]): [User]
    }
    

     
     
    ExecutionInput executionInput = ExecutionInput.newExecutionInput().variables(variable).query("query userQuery($iidd:[Int],$dogId:Int){user(id:$iidd){id,age,dogs(dogId:$dogId){id,dogName}}}").build();
    //DataFetcher
    List id = environment.getArgument("id");
    System.out.println("argument:id=" + id);
    

    Schema 파일 이 여러 개 있 을 때 다음 과 같은 방식 으로 읽 기 융합 을 할 수 있 습 니 다.
     
    File schemaFile1 = loadSchema("starWarsSchemaPart1.graphqls");
    File schemaFile2 = loadSchema("starWarsSchemaPart2.graphqls");
    File schemaFile3 = loadSchema("starWarsSchemaPart3.graphqls");
    
    TypeDefinitionRegistry typeRegistry = new TypeDefinitionRegistry();
    
    // each registry is merged into the main registry
    typeRegistry.merge(schemaParser.parse(schemaFile1));
    typeRegistry.merge(schemaParser.parse(schemaFile2));
    typeRegistry.merge(schemaParser.parse(schemaFile3));
    

     
    두 가지 구조 Schema 방식 을 말 한 후에 우 리 는 장면 을 고려 해 보 겠 습 니 다. 아니면 User 와 Dog 의 데이터 구 조 를 고려 해 보 겠 습 니 다. 만약 에 User 정 보 는 RPC 를 통 해 A 응용 프로그램 에서 얻 어야 하고 Dog 정 보 는 모든 userId 에서 응용 B 의 RPC 서 비 스 를 호출 하여 얻 어야 합 니 다. 어떻게 조작 해 야 합 니까?앞서 몇 편의 글 에서 언급 한 바 와 같이 GraphQL 엔진 은 필드 와 사용자 정의 대상 유형 을 분석 하고 거품 조 회 를 통 해 DataFetcher 를 실행 합 니 다. 스칼라 형식 (즉 기본 형식) 을 만 날 때 까지 dogs 필드 를 분석 할 때 사용자 정의 형식 이기 때문에 자 연 스 럽 게 계 속 됩 니 다.한편, DataFetcher 의 environment 를 통 해 부모 대상 과 그의 메 인 키 속성 userId 를 얻 을 수 있 기 때문에 해결 방안 도 이에 따라 생 겨 났 지만 상대 적 으로 이런 상황 에서 N + 1 의 문 제 를 초래 할 수 있다. schema:
     
    schema {
        query: userQuery
    }
    type userQuery {
        user(id: Int): [User]
    }
    type User {
        id: Int
        age: Int
        userName: String!
        dogs(dogId:Int): [Dog]
    }
    type Dog {
        id: Int
        dogName: String!
    }
    

     
     
    //构建一个运行时Java语义 绑定schema,包括(datafetcher、typeResolver、customScalar)
        private static RuntimeWiring buildRuntimeWiring() {
            //return RuntimeWiring.newRuntimeWiring().wiringFactory(new EchoingWiringFactory()).build();
            return RuntimeWiring.newRuntimeWiring()
                    // this uses builder function lambda syntax
                    .type("userQuery", typeWiring -> typeWiring
                                    .dataFetcher("user", environment -> {
                                        //上一级对象数据 environment.getSource()
                                        //Map environment.getArguments()
                                        //环境上下文,整个查询冒泡中都可以使用 environment.getContext()
                                        Integer id = environment.getArgument("id");
                                        System.out.println("argument:id=" + id);
                                        // repository 处理
                                        return getUsers(id);
                                    })
                    ).type("User", typeWiring -> typeWiring
                            .dataFetcher("dogs", environment -> {
                                //获取父对象
                                User user = environment.getSource();
                                int userId = user.getId();
                                System.out.println("dogs outside userId = "+ userId);
                                Integer paramDogId = environment.getArgument("dogId");
                                System.out.println("dogs inside dogId = "+ paramDogId);
                                //模拟rpc调用
                                List dogs = Lists.newArrayList();
                                return dogs;
                            }))
                            //去掉下面的注释甚至可以改变每个dog对象里id的值
                            //.type("Dog", typeWiring -> typeWiring
                            //.dataFetcher("id", environment -> {
                            //        return 9999;
                            //}))
                    .build();
        }
        //schema
        public static void mainExec() throws InterruptedException {
            //创建Schema
            SchemaParser schemaParser = new SchemaParser();
            SchemaGenerator schemaGenerator = new SchemaGenerator();
    
            File schemaFile = loadSchema("graphql/userDemo.graphqls");
    
            TypeDefinitionRegistry typeRegistry = schemaParser.parse(schemaFile);
            RuntimeWiring wiring = buildRuntimeWiring();
            GraphQLSchema schema = schemaGenerator.makeExecutableSchema(typeRegistry, wiring);
            //测试输出
            GraphQL graphQL = GraphQL.newGraphQL(schema).build();
            Map variable = Maps.newHashMap();
            variable.put("iidd",1);variable.put("dogId",2);
            ExecutionInput executionInput = ExecutionInput.newExecutionInput().variables(variable).
                    query("query userQuery($iidd:Int,$dogId:Int){user(id:$iidd){id,age,dogs(dogId:$dogId){id,dogName}}}").build();
            Map result = graphQL.execute(executionInput).getData();
            System.out.println(result);
        }
    

    console: argument: id = 1 dogs outside userId = 1 dogs inside dogId = 2 {user = [{id = 1, age = 3, dogs = []} 주의: 모든 DateFetcher 와 Arguments 는 Field 에서 DataFetcher 방법 에 대한 입 참 DataFetching Environment 를 정의 해 야 합 니 다. 우 리 는 이 를 통 해 실행 중인 관건 적 인 데 이 터 를 많이 얻 을 수 있 습 니 다. 여기 서 제 가 중요 하 게 생각 하 는 몇 가 지 를 보 여 줍 니 다.다른 분 들 은 소스 코드 를 직접 확인 하 실 수 있 습 니 다.
  • 부모 급 대상 데이터 (dogs 속성 이 있 는 user 인 스 턴 스)
  • 현재 Field 에서 Arguments 가 전달 하 는 단일 매개 변수 ( T getArgument(String name))
  • 현재 Field 의 모든 인자 (Map getArguments ();)
  • 전체 GrapQL 조회 의 환경 컨 텍스트 가 져 오기 ( T getContext ();) 조 회 를 실행 할 때 다음 코드 를 통 해 설정 할 수 있 습 니 다.
     
    ExecutionInput executionInput = ExecutionInput.newExecutionInput().variables(variable).context(object)...
                   query("query userQuery($iidd:Int){user(id:$iidd){id,age,dogs{id,dogName}}}").build();
           Map result = graphQL.execute(executionInput).getData();
    


  • 사용자 상태, 권한 등 전역 환경 변 수 를 가 져 올 필요 가 있 을 때 Context 는 유용 하 게 변 합 니 다. 전체 GraphQL 엔진 이 실행 되 는 과정 에서 모든 Field 의 DataFetcher 를 가 져 오고 처리 하 는 데 도움 을 줄 수 있 습 니 다.
    위의 몇 가지 예 를 통 해 알 수 있 듯 이 IDL 해석 을 통 해 Schema 는 코드 양 과 가 독성 면 에서 자바 로 정의 하 는 것 보다 훨씬 좋 으 므 로 추천 합 니 다.그 다음으로 Schema 에서 정 의 된 type 마다 내부 필드 Field 에 해당 하 는 DataFetcher 를 정의 할 수 있 으 며, 2 차 Field 는 상위 Field 에서 얻 은 결 과 를 가 져 올 수 있 습 니 다. dogs 에서 부모 대상 User 인 스 턴 스 를 가 져 오 는 것 처럼 기본 Property DataFetcher 를 사용 하여 pojo 에 대응 하 는 속성의 get/set 값 을 읽 는 것 이 아 닙 니 다.
    그 다음 에 비동기 장 에서 데 이 터 를 얻 는 데 성능 이 더욱 좋 거나 최적화 되 는 방안 을 소개 하고 백 엔 드 데이터 로 인해 발생 할 수 있 는 N + 1 문제 와 잠재 적 인 내장 조 회 를 해결 할 것 입 니 다.
    4 월 23 일 업데이트: 또는 IDL 이 Schema 를 정의 하 는 예 입 니 다. 오늘 은 외부 에서 사용 하 는 최상 위 에 노출 된 Field API 만 가 져 오 는 논 리 를 시험 해 보 았 습 니 다. 사용자 정의 형식 으로 사용자 정의 DataFetcher 를 만 들 지 않 아 도 기본 Property DataFetcher 를 사용 하여 정상적으로 조회 할 수 있 고 심지어 IDL 파일 에 인자 가 필요 한 Field 에 대해 서도 DataFetcher 를 정의 하지 않 아 도 됩 니 다.기본적으로 모든 쿼 리 에 대응 할 수 있 습 니 다.IDL 이나 자바 가 정의 하지 않 은 인 자 를 입력 하면 인증 에 실패 할 수 있 습 니 다.반대로 파 라 메 터 를 정 의 했 지만 검색 어 에 들 어 오지 않 고 정상적으로 실행 할 수 있 습 니 다.

    좋은 웹페이지 즐겨찾기