GraphQL Java - Schema


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
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM