GraphQL Java - Schema
Schema
创建一个schema
GraphQL API具有一个Schema,该Schema定义了可以Query(查询)或Mutation(变更)的每个字段以及这些字段的类型。
graphql-java提供了两种不同的定义schema的方式:编程方式编写,和使用graphql dsl语法(也称为SDL)编写。
例如:
SDL示例:
type Foo {
bar: String
}
Java代码示例:
GraphQLObjectType fooType = newObject()
.name("Foo")
.field(newFieldDefinition()
.name("bar")
.type(GraphQLString))
.build();
DataFetcher和TypeResolver
DataFetcher用于获取字段(field)对应的数据。另外,如果是Mutation(变更)类型,则可用于更新数据。
GraphQL中的每个字段(Field Definition)都有一个DataFetcher。如果未指定DataFetcher,则该字段启用默认的PropertyDataFetcher。
PropertyDataFetcher从Map和Java Bean中获取数据。当字段名称与Map中的key或bean对象的属性相同时,无需显式指定DataFetcher。
TypeResolver(类型解析器)用于帮助graphql-java判断数据的实际类型。例如对于Interface和Union类型,TypeResolver用于确定最终获取到的对象属于Interface(接口)的哪个实现,或Union(联合)中的哪种具体类型。
例如,假定你有一个Interface类型叫做MagicUserType,有一系列实现该接口的具体类型:Wizard、Witch和Necomancer。TypeResolver(类型解析器)用于在运行时识别出数据的具体类型(Type),进而决定调用哪个DataFetcher和字段。
new TypeResolver() {
@Override
public GraphQLObjectType getType(TypeResolutionEnvironment env) {
Object javaObject = env.getObject();
if (javaObject instanceof Wizard) {
return env.getSchema().getObjectType("WizardType");
} else if (javaObject instanceof Witch) {
return env.getSchema().getObjectType("WitchType");
} else {
return env.getSchema().getObjectType("NecromancerType");
}
}
};
使用SDL创建一个schema
通过SDL定义模式时,需提供DataFetcher和TypeResolver。
例如,对于如下的schema定义:(starWarsSchema.graphqls)
schema {
query: QueryType
}
type QueryType {
hero(episode: Episode): Character
human(id : String) : Human
droid(id: ID!): Droid
}
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
homePlanet: String
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
这个schema定义包含了字段(field)和类型(type)定义,但是仍需要一个“运行时绑定”(runtime wiring),将它绑定到Java方法中,使它成为一个完全可执行的schema。
可以使用如下的代码完成绑定(wiring)过程:
RuntimeWiring buildRuntimeWiring() {
return RuntimeWiring.newRuntimeWiring()
.scalar(CustomScalar)
// this uses builder function lambda syntax
.type("QueryType", typeWiring -> typeWiring
.dataFetcher("hero", new StaticDataFetcher(StarWarsData.getArtoo()))
.dataFetcher("human", StarWarsData.getHumanDataFetcher())
.dataFetcher("droid", StarWarsData.getDroidDataFetcher())
)
.type("Human", typeWiring -> typeWiring
.dataFetcher("friends", StarWarsData.getFriendsDataFetcher())
)
// you can use builder syntax if you don't like the lambda syntax
.type("Droid", typeWiring -> typeWiring
.dataFetcher("friends", StarWarsData.getFriendsDataFetcher())
)
// or full builder syntax if that takes your fancy
.type(
newTypeWiring("Character")
.typeResolver(StarWarsData.getCharacterTypeResolver())
.build()
)
.build();
}
最后,你需要通过将schema文件和“绑定”(wiring)结合,创建一个可执行的schema。示例如下:
SchemaParser schemaParser = new SchemaParser();
SchemaGenerator schemaGenerator = new SchemaGenerator();
File schemaFile = loadSchema("starWarsSchema.graphqls");
TypeDefinitionRegistry typeRegistry = schemaParser.parse(schemaFile);
RuntimeWiring wiring = buildRuntimeWiring();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeRegistry, wiring);
除了使用上面的build方式之外,TypeResolver和DataFetcher也可以使用WiringFactory接口完成绑定。
示例代码如下:
RuntimeWiring buildDynamicRuntimeWiring() {
WiringFactory dynamicWiringFactory = new WiringFactory() {
@Override
public boolean providesTypeResolver(TypeDefinitionRegistry registry, InterfaceTypeDefinition definition) {
return getDirective(definition,"specialMarker") != null;
}
@Override
public boolean providesTypeResolver(TypeDefinitionRegistry registry, UnionTypeDefinition definition) {
return getDirective(definition,"specialMarker") != null;
}
@Override
public TypeResolver getTypeResolver(TypeDefinitionRegistry registry, InterfaceTypeDefinition definition) {
Directive directive = getDirective(definition,"specialMarker");
return createTypeResolver(definition,directive);
}
@Override
public TypeResolver getTypeResolver(TypeDefinitionRegistry registry, UnionTypeDefinition definition) {
Directive directive = getDirective(definition,"specialMarker");
return createTypeResolver(definition,directive);
}
@Override
public boolean providesDataFetcher(TypeDefinitionRegistry registry, FieldDefinition definition) {
return getDirective(definition,"dataFetcher") != null;
}
@Override
public DataFetcher getDataFetcher(TypeDefinitionRegistry registry, FieldDefinition definition) {
Directive directive = getDirective(definition, "dataFetcher");
return createDataFetcher(definition,directive);
}
};
return RuntimeWiring.newRuntimeWiring()
.wiringFactory(dynamicWiringFactory).build();
}
编程式构建schema
以编程方式创建模式时,将在创建类型时提供DataFetcher和TypeResolver:
示例代码如下:
DataFetcher<Foo> fooDataFetcher = new DataFetcher<Foo>() {
@Override
public Foo get(DataFetchingEnvironment environment) {
// environment.getSource() is the value of the surrounding
// object. In this case described by objectType
Foo value = perhapsFromDatabase(); // Perhaps getting from a DB or whatever
return value;
}
};
GraphQLObjectType objectType = newObject()
.name("ObjectType")
.field(newFieldDefinition()
.name("foo")
.type(GraphQLString)
)
.build();
GraphQLCodeRegistry codeRegistry = newCodeRegistry()
.dataFetcher(
coordinates("ObjectType", "foo"),
fooDataFetcher)
.build();
类型(Type)
Graphql类型系统支持如下几种类型
- Scalar
- Object
- Interface
- Union
- InputObject
- Enum
Scalar
graphql-java支持如下的Scalars:
-
标准的graphql scalars:GraphQLString、GraphQLBoolean、GraphQLInt、GraphQLFloat、GraphQLID
-
graph-java扩展的Scalar:
- GraphQLLong
- GraphQLShort
- GraphQLByte
- GraphQLFloat
- GraphQLBigDecimal
- GraphQLBigInteger
注意,扩展的标量的语义,可能无法被graphql的客户端所正确理解。例如,将Java Lang(最大值263-1)转换为JavaScript数字(最大值253-1),可能会产生问题。
Object
SDL示例如下:
type SimpsonCharacter {
name: String
mainCharacter: Boolean
}
Java示例如下:
GraphQLObjectType simpsonCharacter = newObject()
.name("SimpsonCharacter")
.description("A Simpson character")
.field(newFieldDefinition()
.name("name")
.description("The name of the character.")
.type(GraphQLString))
.field(newFieldDefinition()
.name("mainCharacter")
.description("One of the main Simpson characters?")
.type(GraphQLBoolean))
.build();
Interface
Interface是抽象类型的定义。
SDL示例如下:
interface ComicCharacter {
name: String;
}
Java示例如下:
GraphQLInterfaceType comicCharacter = newInterface()
.name("ComicCharacter")
.description("An abstract comic character.")
.field(newFieldDefinition()
.name("name")
.description("The name of the character.")
.type(GraphQLString))
.build();
Union
SDL示例如下:
type Cat {
name: String;
lives: Int;
}
type Dog {
name: String;
bonesOwned: int;
}
union Pet = Cat | Dog
Java示例如下:
TypeResolver typeResolver = new TypeResolver() {
@Override
public GraphQLObjectType getType(TypeResolutionEnvironment env) {
if (env.getObject() instanceof Cat) {
return CatType;
}
if (env.getObject() instanceof Dog) {
return DogType;
}
return null;
}
};
GraphQLUnionType PetType = newUnionType()
.name("Pet")
.possibleType(CatType)
.possibleType(DogType)
.build();
GraphQLCodeRegistry codeRegistry = newCodeRegistry()
.typeResolver("Pet", typeResolver)
.build();
Enum
SDL示例:
enum Color {
RED
GREEN
BLUE
}
Java示例如下:
GraphQLEnumType colorEnum = newEnum()
.name("Color")
.description("Supported colors.")
.value("RED")
.value("GREEN")
.value("BLUE")
.build();
ObjectInputType
SDL示例:
input Character {
name: String
}
Java示例如下:
GraphQLInputObjectType inputObjectType = newInputObject()
.name("inputObjectType")
.field(newInputObjectField()
.name("field")
.type(GraphQLString))
.build();
Type References(类型引用,可用于创建递归类型)
GraphQL支持递归类型。例如,Person类可能包含一系列相同类型的friends。
为了支持这样的类型,graphql-java提供了GraphQLTypeReference类。
当schema被创建时,GraphQLTypeReference会使用替换为真实的类型。
例如:
GraphQLObjectType person = newObject()
.name("Person")
.field(newFieldDefinition()
.name("friends")
.type(GraphQLList.list(GraphQLTypeReference.typeRef("Person"))))
.build();
如果schema使用SDL创建,name递归类型无需被显示处理。graphql会自动检测出来。
Schema SDL模块化
维护一个较大的schema文件不是可行的,graphql-java也提供了两种方式,可以针对schema进行模块化。
第一种方法是将多个Schema SDL文件合并为一个逻辑单元。 在下面的情况下,Schema拆分为多个文件,并在Schema生成之前合并在一起。
SchemaParser schemaParser = new SchemaParser();
SchemaGenerator schemaGenerator = new SchemaGenerator();
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));
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeRegistry, buildRuntimeWiring());
Graphql SDL类型系统具有另一种方法用于模块化模式的构造。 可以使用类型扩展来为类型添加额外的字段和接口。
假设在一个模式文件中以这样的类型开始:
type Human {
id: ID!
name: String!
}
系统中的另一部分可以对这个类型进行扩展,并且增加更多的字段。例如:
extend type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
可以使用尽可能多的扩展。它们将会以被发现的顺序进行合并。重复的字段将会被合并为一个。
extend type Human {
homePlanet: String
}
以上的多个schema文件,在运行时合并为一个Human类型,如下:
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
homePlanet: String
}
这在schema的顶层设计时十分重要。你可以使用扩展类型,来为顶层的schema中的”query“添加新的字段。
团队可以为顶层的graphql查询独立的进行各自的模块功能实现。
schema {
query: CombinedQueryFromMultipleTeams
}
type CombinedQueryFromMultipleTeams {
createdTimestamp: String
}
# maybe the invoicing system team puts in this set of attributes
extend type CombinedQueryFromMultipleTeams {
invoicing: Invoicing
}
# and the billing system team puts in this set of attributes
extend type CombinedQueryFromMultipleTeams {
billing: Billing
}
# and so and so forth
extend type CombinedQueryFromMultipleTeams {
auditing: Auditing
}
订阅支持
订阅允许你进行查询,并且当相关查询的后端对象有所变更时,更新后的对象会实时推送过来。
subscription foo {
# normal graphql query
}