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
    }
相關文章
相關標籤/搜索