Graphql - Java 실천 (1) - 조회
16907 단어 Graphql-X
단순 조회
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 를 정의 해 야 합 니 다. 우 리 는 이 를 통 해 실행 중인 관건 적 인 데 이 터 를 많이 얻 을 수 있 습 니 다. 여기 서 제 가 중요 하 게 생각 하 는 몇 가 지 를 보 여 줍 니 다.다른 분 들 은 소스 코드 를 직접 확인 하 실 수 있 습 니 다.
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 이나 자바 가 정의 하지 않 은 인 자 를 입력 하면 인증 에 실패 할 수 있 습 니 다.반대로 파 라 메 터 를 정 의 했 지만 검색 어 에 들 어 오지 않 고 정상적으로 실행 할 수 있 습 니 다.