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 }