graphql-java使用手册: part2 创建Schema
原文:http://blog.mygraphql.com/wordpress/?p=100
创建Schema
Schema的主要用途是定义所有可供查询的字段(field),它们最终组合成一套完整的GraphQL
API.
“graphql-java”提供两种方法来定义Schema。用java代码来定义、用GraphQL
SDL(即IDL)来定义。
注意:SDL(IDL)现在还不是 官方 graphql 规范. 本GraphQL实现,是基于
已有的JS参考实现
来开发的。但JS参考实现中的很多代码也是基于SDL(IDL)语法的,所以你可以认为这语法是可以长期使用的.
如果你不确认用“java代码”还是用“GraphQL
SDL(即IDL)”来定义你的Schema,那么我们建议你用SDL(IDL)
SDL example:
type Foo { bar: String }
java代码例子:
GraphQLObjectType fooType = newObject() .name("Foo") .field(newFieldDefinition() .name("bar") .type(GraphQLString)) .build();
DataFetcher 与 TypeResolver
对象 DataFetcher
作用是获取字段(field)对应的数据;另外,在修改(mutation)操作时,可以更新数据
每个字段都有自己的 DataFetcher
. 如果未为字段指定DataFetcher,
那么自动使用默认的 PropertyDataFetcher .
PropertyDataFetcher
从 Map
和 Java Beans 中获取数据.
所以,当Schema中的field名,与Map中的key值,或 Source Object
中的 java
bean 字段名相同时,不需要为field指定 DataFetcher
.
对象 TypeResolver
帮助 graphql-java
判断数据的实际类型(type). 所以Interface
和 Union
均需要指定关联的 TypeResolver(类型识别器)
.
例如,你有一个 Interface
叫 MagicUserType
它有可能是以下的具体类型(Type) Wizard, Witch and Necromancer.
Type resolver(类型识别器) 的作用是在运行时识别出 GraphqlObjectType
的具体类型(Type)。后期具体类型下的field相关的 data
fetcher被调用并获取数据.
new TypeResolver() { @Override public GraphQLObjectType getType(TypeResolutionEnvironment env) { Object javaObject = env.getObject(); if (javaObject instanceof Wizard) { return (GraphQLObjectType) env.getSchema().getType("WizardType"); } else if (javaObject instanceof Witch) { return (GraphQLObjectType) env.getSchema().getType("WitchType"); } else { return (GraphQLObjectType) env.getSchema().getType("NecromancerType"); } } };
用 SDL 创建 Schema
当使用SDL方法来开发时,你需要同时编写对应的 DataFetcher
和TypeResolver
。
很大的 Schema IDL 文件很难查看。
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中只是指定了静态的字段和类型,你还需要把它绑定到java方法中。以让Schema可以运行起来
这里的绑定,包括 DataFetcher
, TypeResolvers
与自定义 Scalar
.
用下页的Builder方法,就可以绑定Schema和Java程序
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);
除了上面的 builder 风格, TypeResolver
s 与 DataFetcher
s 也可以通过WiringFactory
接口绑定在一起。通过程序去分析 SDL
,就可以允许更自由的绑定。你可以 通过分析 SDL 声明, 或其它 SDL
定义去决定你的运行时逻辑。
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
如果用程序方式来定义 Schema,在创建类型(type)的时候,你需要提供DataFetcher
and TypeResolver
。
如:
DataFetcher<Foo> fooDataFetcher = 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) .dataFetcher(fooDataFetcher)) .build();
类型(Types)
GraphQL 类型系统支持以下类型
- Scalar
- Object
- Interface
- Union
- InputObject
- Enum
Scalar
graphql-java
支持以下基本数据类型( Scalars)。
GraphQLString
GraphQLBoolean
GraphQLInt
GraphQLFloat
GraphQLID
GraphQLLong
GraphQLShort
GraphQLByte
GraphQLFloat
GraphQLBigDecimal
GraphQLBigInteger
Object
SDL Example:
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
Interfaces 是抽象的 类型( types)定义.
SDL Example:
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 Example:
interface Cat { name: String; lives: Int; } interface Dog { name: String; bonesOwned: int; } union Pet = Cat | Dog
Java 例子:
GraphQLUnionType PetType = newUnionType() .name("Pet") .possibleType(CatType) .possibleType(DogType) .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; } }) .build();
Enum
SDL Example:
enum Color { RED GREEN BLUE }
Java 例子:
GraphQLEnumType colorEnum = newEnum() .name("Color") .description("Supported colors.") .value("RED") .value("GREEN") .value("BLUE") .build();
ObjectInputType
SDL Example:
input Character { name: String }
Java 例子:
GraphQLInputObjectType inputObjectType = newInputObject() .name("inputObjectType") .field(newInputObjectField() .name("field") .type(GraphQLString)) .build();
类型引用 (Type References) (递归类型recursive types)
GraphQL 支持递归类型:如 Person(人)
可以包含很多朋友【译注:当然这些也是人类型的】
为了方便声明这种情况, graphql-java
有一个 GraphQLTypeReference
类。
在实际的 Schema 创建时,GraphQLTypeReference
会变为实际的类型。
例如:
GraphQLObjectType person = newObject() .name("Person") .field(newFieldDefinition() .name("friends") .type(new GraphQLList(new GraphQLTypeReference("Person")))) .build();
如果用SDL(ID L)来定义 Schema ,不需要特殊的处理。
Schema IDL的模块化
很大的 Schema IDL 文件很难查看。所以我们有两种方法可以模块化 Schema。
方法一是合并多个 Schema IDL 文件到一个逻辑单元( logic
unit)。下面的例子是,在 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 IDL 还有其它方法去做模块化。你可以使用 type extensions
去为现有类型增加字段和 interface。
例如,一开始,你有这样一个文件:
type Human { id: ID! name: String! }
你的系统的其它模块可以扩展这个类型:
extend type Human implements Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]! }
你可以按你的需要去扩展。它们会以被发现的顺序组合起来。重复的字段会被合并(但重定义一个字段的类型是不允许的)。
extend type Human { homePlanet: String }
完成合并后的 Human 类型会是这样的:
type Human implements Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]! homePlanet: String }
这在顶层查询中特别有用。你可以用 extension types 去为顶层 “query”
增加字段每个团队可以提供自己的字段集,进而合成完整的查询。
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(订阅)的支持
订阅功能还未在规范中: graphql-java
现在只支持简单的实现 ,你可以用GraphQLSchema.Builder.subscription(...)
在 Schema
中定义订阅。这使你可以处理订阅请求。
subscription foo { # normal graphql query }