graphql-java使用手冊(cè): part2 創(chuàng)建Schema

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

創(chuàng)建Schema

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

“graphql-java”提供兩種方法來(lái)定義Schema凌唬。用java代碼來(lái)定義幻妓、用GraphQL
SDL(即IDL)來(lái)定義。

注意:SDL(IDL)現(xiàn)在還不是 官方 graphql 規(guī)范. 本GraphQL實(shí)現(xiàn)忱嘹,是基于
已有的JS參考實(shí)現(xiàn)
來(lái)開(kāi)發(fā)的。但JS參考實(shí)現(xiàn)中的很多代碼也是基于SDL(IDL)語(yǔ)法的,所以你可以認(rèn)為這語(yǔ)法是可以長(zhǎng)期使用的.

如果你不確認(rèn)用“java代碼”還是用“GraphQL
SDL(即IDL)”來(lái)定義你的Schema仍律,那么我們建議你用SDL(IDL)

SDL example:

type Foo {
    bar: String
}

java代碼例子:

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

DataFetcher 與 TypeResolver

對(duì)象 DataFetcher
作用是獲取字段(field)對(duì)應(yīng)的數(shù)據(jù);另外实柠,在修改(mutation)操作時(shí)水泉,可以更新數(shù)據(jù)

每個(gè)字段都有自己的 DataFetcher. 如果未為字段指定DataFetcher,
那么自動(dòng)使用默認(rèn)的 PropertyDataFetcher .

PropertyDataFetcherMap 和 Java Beans 中獲取數(shù)據(jù).
所以,當(dāng)Schema中的field名窒盐,與Map中的key值草则,或 Source Object 中的 java
bean 字段名相同時(shí),不需要為field指定 DataFetcher.

對(duì)象 TypeResolver 幫助 graphql-java 判斷數(shù)據(jù)的實(shí)際類(lèi)型(type). 所以
InterfaceUnion 均需要指定關(guān)聯(lián)的 TypeResolver(類(lèi)型識(shí)別器) .

例如蟹漓,你有一個(gè) InterfaceMagicUserType
它有可能是以下的具體類(lèi)型(Type) Wizard, Witch and Necromancer.
Type resolver(類(lèi)型識(shí)別器) 的作用是在運(yùn)行時(shí)識(shí)別出 GraphqlObjectType
的具體類(lèi)型(Type)炕横。后期具體類(lèi)型下的field相關(guān)的 data
fetcher被調(diào)用并獲取數(shù)據(jù).

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 創(chuàng)建 Schema

當(dāng)使用SDL方法來(lái)開(kāi)發(fā)時(shí),你需要同時(shí)編寫(xiě)對(duì)應(yīng)的 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中只是指定了靜態(tài)的字段和類(lèi)型,你還需要把它綁定到j(luò)ava方法中嗽交。以讓Schema可以運(yùn)行起來(lái)

這里的綁定卿嘲,包括 DataFetcher , TypeResolvers 與自定義 Scalar.

用下頁(yè)的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();
}

最后轮纫,你可以通過(guò)整合靜態(tài) Schema 和 綁定(wiring)腔寡,而生成一個(gè)可以執(zhí)行的
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 風(fēng)格, TypeResolver s 與 DataFetcher s 也可以通過(guò)
WiringFactory 接口綁定在一起掌唾。通過(guò)程序去分析 SDL
放前,就可以允許更自由的綁定忿磅。你可以 通過(guò)分析 SDL 聲明, 或其它 SDL
定義去決定你的運(yùn)行時(shí)邏輯。

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();
}

用代碼方式創(chuàng)建 schema

如果用程序方式來(lái)定義 Schema凭语,在創(chuàng)建類(lèi)型(type)的時(shí)候葱她,你需要提供
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();

類(lèi)型(Types)

GraphQL 類(lèi)型系統(tǒng)支持以下類(lèi)型

  • Scalar
  • Object
  • Interface
  • Union
  • InputObject
  • Enum

Scalar

graphql-java 支持以下基本數(shù)據(jù)類(lèi)型( 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 是抽象的 類(lèi)型( 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();

類(lèi)型引用 (Type References) (遞歸類(lèi)型recursive types)

GraphQL 支持遞歸類(lèi)型:如 Person(人)
可以包含很多朋友【譯注:當(dāng)然這些也是人類(lèi)型的】

為了方便聲明這種情況吨些, graphql-java 有一個(gè) GraphQLTypeReference 類(lèi)。

在實(shí)際的 Schema 創(chuàng)建時(shí)炒辉,GraphQLTypeReference 會(huì)變?yōu)閷?shí)際的類(lèi)型豪墅。

例如:

GraphQLObjectType person = newObject()
    .name("Person")
    .field(newFieldDefinition()
            .name("friends")
            .type(new GraphQLList(new GraphQLTypeReference("Person"))))
    .build();

如果用SDL(ID L)來(lái)定義 Schema ,不需要特殊的處理黔寇。

Schema IDL的模塊化

很大的 Schema IDL 文件很難查看偶器。所以我們有兩種方法可以模塊化 Schema。

方法一是合并多個(gè) Schema IDL 文件到一個(gè)邏輯單元( logic
unit)缝裤。下面的例子是屏轰,在 Schema 生成前,合并多個(gè)獨(dú)立的文件憋飞。

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
去為現(xiàn)有類(lèi)型增加字段和 interface。

例如榛做,一開(kāi)始唁盏,你有這樣一個(gè)文件:

type Human {
    id: ID!
    name: String!
}

你的系統(tǒng)的其它模塊可以擴(kuò)展這個(gè)類(lèi)型:

extend type Human implements Character {
    id: ID!
    name: String!
    friends: [Character]
    appearsIn: [Episode]!
}

你可以按你的需要去擴(kuò)展。它們會(huì)以被發(fā)現(xiàn)的順序組合起來(lái)瘤睹。重復(fù)的字段會(huì)被合并(但重定義一個(gè)字段的類(lèi)型是不允許的)升敲。

extend type Human {
    homePlanet: String
}

完成合并后的 Human 類(lèi)型會(huì)是這樣的:

type Human implements Character {
    id: ID!
    name: String!
    friends: [Character]
    appearsIn: [Episode]!
    homePlanet: String
}

這在頂層查詢(xún)中特別有用答倡。你可以用 extension types 去為頂層 “query”
增加字段每個(gè)團(tuán)隊(duì)可以提供自己的字段集轰传,進(jìn)而合成完整的查詢(xún)。

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(訂閱)的支持

訂閱功能還未在規(guī)范中: graphql-java 現(xiàn)在只支持簡(jiǎn)單的實(shí)現(xiàn) 瘪撇,你可以用
GraphQLSchema.Builder.subscription(...) 在 Schema
中定義訂閱获茬。這使你可以處理訂閱請(qǐng)求。

subscription foo {
    # normal graphql query
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末倔既,一起剝皮案震驚了整個(gè)濱河市恕曲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渤涌,老刑警劉巖佩谣,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異实蓬,居然都是意外死亡茸俭,警方通過(guò)查閱死者的電腦和手機(jī)吊履,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)调鬓,“玉大人艇炎,你說(shuō)我怎么就攤上這事√谖眩” “怎么了缀踪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)虹脯。 經(jīng)常有香客問(wèn)我驴娃,道長(zhǎng),這世上最難降的妖魔是什么循集? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任托慨,我火速辦了婚禮,結(jié)果婚禮上暇榴,老公的妹妹穿的比我還像新娘厚棵。我一直安慰自己,他們只是感情好蔼紧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布婆硬。 她就那樣靜靜地躺著,像睡著了一般奸例。 火紅的嫁衣襯著肌膚如雪彬犯。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天查吊,我揣著相機(jī)與錄音谐区,去河邊找鬼。 笑死逻卖,一個(gè)胖子當(dāng)著我的面吹牛宋列,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播评也,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼炼杖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了盗迟?” 一聲冷哼從身側(cè)響起坤邪,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎罚缕,沒(méi)想到半個(gè)月后艇纺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年黔衡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了消约。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡员帮,死狀恐怖或粮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捞高,我是刑警寧澤氯材,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站硝岗,受9級(jí)特大地震影響氢哮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜型檀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一冗尤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胀溺,春花似錦裂七、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至无埃,卻和暖如春徙瓶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嫉称。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工侦镇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人织阅。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓壳繁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蒲稳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子氮趋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容