GraphQL JAVA - Schema

fancie
2023-12-01

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:

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

  2. 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
    }

 类似资料: