GraphQL Java - Schema

Schema

建立一个schema

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和TypeResolver

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建立一个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:

  1. 标准的graphql scalars:GraphQLString、GraphQLBoolean、GraphQLInt、GraphQLFloat、GraphQLID

  2. graph-java扩展的Scalar:

    • GraphQLLong
    • GraphQLShort
    • GraphQLByte
    • GraphQLFloat
    • GraphQLBigDecimal
    • GraphQLBigInteger

    注意,扩展的标量的语义,可能没法被graphql的客户端所正确理解。例如,将Java Lang(最大值26^3-1)转换为JavaScript数字(最大值2^53-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
    }
相关文章
相关标签/搜索