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
}