GraphQL API具备一个Schema,该Schema定义了能够Query(查询)或Mutation(变动)的每一个字段以及这些字段的类型。java
graphql-java提供了两种不一样的定义schema的方式:编程方式编写,和使用graphql dsl语法(也称为SDL)编写。编程
例如:后端
SDL示例:app
type Foo { bar: String }
Java代码示例:ide
GraphQLObjectType fooType = newObject() .name("Foo") .field(newFieldDefinition() .name("bar") .type(GraphQLString)) .build();
DataFetcher用于获取字段(field)对应的数据。另外,若是是Mutation(变动)类型,则可用于更新数据。模块化
GraphQL中的每一个字段(Field Definition)都有一个DataFetcher。若是未指定DataFetcher,则该字段启用默认的PropertyDataFetcher。ui
PropertyDataFetcher从Map和Java Bean中获取数据。当字段名称与Map中的key或bean对象的属性相同时,无需显式指定DataFetcher。this
TypeResolver(类型解析器)用于帮助graphql-java判断数据的实际类型。例如对于Interface和Union类型,TypeResolver用于肯定最终获取到的对象属于Interface(接口)的哪一个实现,或Union(联合)中的哪一种具体类型。spa
例如,假定你有一个Interface类型叫作MagicUserType,有一系列实现该接口的具体类型:Wizard、Witch和Necomancer。TypeResolver(类型解析器)用于在运行时识别出数据的具体类型(Type),进而决定调用哪一个DataFetcher和字段。scala
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定义模式时,需提供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(); }
以编程方式建立模式时,将在建立类型时提供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();
Graphql类型系统支持以下几种类型
graphql-java支持以下的Scalars:
标准的graphql scalars:GraphQLString、GraphQLBoolean、GraphQLInt、GraphQLFloat、GraphQLID
graph-java扩展的Scalar:
注意,扩展的标量的语义,可能没法被graphql的客户端所正确理解。例如,将Java Lang(最大值26^3-1)转换为JavaScript数字(最大值2^53-1),可能会产生问题。
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是抽象类型的定义。
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();
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();
SDL示例:
enum Color { RED GREEN BLUE }
Java示例以下:
GraphQLEnumType colorEnum = newEnum() .name("Color") .description("Supported colors.") .value("RED") .value("GREEN") .value("BLUE") .build();
SDL示例:
input Character { name: String }
Java示例以下:
GraphQLInputObjectType inputObjectType = newInputObject() .name("inputObjectType") .field(newInputObjectField() .name("field") .type(GraphQLString)) .build();
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文件不是可行的,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 }