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 .

PropertyDataFetcherMap 和 Java Beans 中获取数据.
所以,当Schema中的field名,与Map中的key值,或 Source Object 中的 java
bean 字段名相同时,不需要为field指定 DataFetcher.

对象 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
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容