graphql-java使用手冊: part2 建立Schema

原文:http://blog.mygraphql.com/wordpress/?p=100java

建立Schema

Schema的主要用途是定義全部可供查詢的字段(field),它們最終組合成一套完整的GraphQL
API.app

「graphql-java」提供兩種方法來定義Schema。用java代碼來定義、用GraphQL
SDL(即IDL)來定義。ide

注意:SDL(IDL)如今還不是 官方 graphql 規範. 本GraphQL實現,是基於
已有的JS參考實現
來開發的。但JS參考實現中的不少代碼也是基於SDL(IDL)語法的,因此你能夠認爲這語法是能夠長期使用的.模塊化

若是你不確認用「java代碼」仍是用「GraphQL
SDL(即IDL)」來定義你的Schema,那麼咱們建議你用SDL(IDL)wordpress

SDL example:fetch

type Foo {
    bar: String
}

java代碼例子:ui

GraphQLObjectType fooType = newObject()
    .name("Foo")
    .field(newFieldDefinition()
            .name("bar")
            .type(GraphQLString))
    .build();

DataFetcher 與 TypeResolver

對象 DataFetcher
做用是獲取字段(field)對應的數據;另外,在修改(mutation)操做時,能夠更新數據this

每一個字段都有本身的 DataFetcher. 若是未爲字段指定DataFetcher,
那麼自動使用默認的 PropertyDataFetcher .scala

PropertyDataFetcherMap 和 Java Beans 中獲取數據.
因此,當Schema中的field名,與Map中的key值,或 Source Object 中的 java
bean 字段名相同時,不須要爲field指定 DataFetcher.code

對象 TypeResolver 幫助 graphql-java 判斷數據的實際類型(type). 因此
InterfaceUnion 均須要指定關聯的 TypeResolver(類型識別器) .

例如,你有一個 InterfaceMagicUserType
它有多是如下的具體類型(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
}
相關文章
相關標籤/搜索