1. 程式人生 > >記錄初學Spring boot中使用GraphQL編寫API的幾種方式

記錄初學Spring boot中使用GraphQL編寫API的幾種方式

tor resolv dev star ase tps food env cut

Spring boot+graphql

一、使用graphql-java-tools方式

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-java-tools</artifactId>
    <version>5.6.0</version>
</dependency>

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>5.0.4</version>
</dependency>

schema.graphqls

type Query {
    books: [Book!]
}

type Book {
    id: Int!
    name: String!
    author: Author!
}

type Author {
    id: Int!
    name: String!
}

對應的java class

class Book {
    private int id;
    private String name;
    private int authorId;

    // constructor

    // getId
    // getName
    // getAuthorId
}

class Author {
    private int id;
    private String name;

    // constructor

    // getId
    // getName
}

Book-Resolver

class BookResolver implements GraphQLResolver<Book> {

    private AuthorRepository authorRepository;

    public BookResolver(AuthorRepository authorRepository) {
        this.authorRepository = authorRepository;
    }

    public Author author(Book book) {
        return authorRepository.findById(book.getAuthorId());
    }
}

Query-Resolver

class Query implements GraphQLQueryResolver {

    private BookRepository bookRepository;

    public Query(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public List<Book> books() {
        return bookRepository.findAll();
    }
}

Type Query 沒有對應的java class,如果type 中的所有字段和java class成員變量一致,則該type可以不用定義Resolver.

graphql type中的字段映射Java class字段的優先級

對於graphql objectType中的字段映射為java class字段順序如下:

  1. method (*fieldArgs [, DataFetchingEnvironment])
  2. method is(*fieldArgs [, DataFetchingEnvironment]), only if the field returns a Boolean
  3. method get(*fieldArgs [, DataFetchingEnvironment])
  4. method getField(*fieldArgs [, DataFetchingEnvironment])
  5. field

例如:

上述type Book中的name字段的映射順序為:

  1. 在java Book類中找name(參數)方法。
  2. 如果Book類中沒有name(參數)方法,則繼續找isName(參數)方法。
  3. 如果Book中沒有isName(參數)方法,則繼續在Book中找getName(參數)方法。
  4. 如果Book中沒有getName()方法,則繼續在Book中找getFieldName(參數)方法。
  5. 如果Book中沒有getFieldName(參數)方法,在繼續在Book中找name成員變量。
  6. 如果Book中沒有name成員變量,則報錯。

graphql type中的字段映射Resolver的優先級:

  1. method (dataClassInstance, *fieldArgs [, DataFetchingEnvironment])
  2. method is(dataClassInstance, *fieldArgs [, DataFetchingEnvironment]), only if the field returns a Boolean
  3. method get(dataClassInstance, *fieldArgs [, DataFetchingEnvironment])
  4. method getField(dataClassInstance, *fieldArgs [, DataFetchingEnvironment])

註:Resolver的映射優先級高於Java Class,首先在Resolver中查找,如果沒找到,才會在Java class中查找 :例如上述type Book中的author字段,會首先映射為BookResolver重的author(Book)方法。

解析schema.graphqls,創建Graphql對象:

import com.coxautodev.graphql.tools.SchemaParser;

GraphQLSchema schema = SchemaParser.newParser().file("schema.graphqls")
        .resolvers(new QueryResolver(), new BookResolver())
        .build()
        .makeExecutableSchema();

GraphQL graphQL = GraphQL.newGraphQL(schema).build();
// 執行查詢
ExecutionResult result = graphQL.execute(query);

Map<String, Object> map = result.toSpecification();

二、不使用Resolver

schema.graphqls:

type Query {
  bookById(id: ID): Book 
}

type Book {
  id: ID
  name: String
  pageCount: Int
  author: Author
}

type Author {
  id: ID
  firstName: String
  lastName: String
}

加載schema.graphqls,創建GraphQL對象:

import graphql.schema.idl.SchemaParser;

@Value("classpath:schema.graphqls")
Resource resource;

@PostConstruct
private void loadSchema() throws Exception {
    File schemaFile = resource.getFile();

    GraphQLSchema schema = buildSchema(schemaFile);

    graphQL = GraphQL.newGraphQL(schema).build();
}

private GraphQLSchema buildSchema(File file) throws Exception {
    TypeDefinitionRegistry registry = new SchemaParser().parse(file);
    RuntimeWiring runtimeWiring = buildWiring();
    SchemaGenerator schemaGenerator = new SchemaGenerator();
    return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}

private RuntimeWiring buildWiring() {
    return RuntimeWiring.newRuntimeWiring()
        // 為每個graphql type的字段提供DataFetcher
        .type(newTypeWiring("Query")
              .dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
        .type(newTypeWiring("Book")
              .dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
        .build();
}

DataFetcher:

@Component
public class GraphQLDataFetchers {

    private static List<Map<String, String>> books = Arrays.asList(
            ImmutableMap.of("id", "book-1",
                    "name", "Harry Potter and the Philosopher's Stone",
                    "pageCount", "223",
                    "authorId", "author-1"),
            ImmutableMap.of("id", "book-2",
                    "name", "Moby Dick",
                    "pageCount", "635",
                    "authorId", "author-2"),
            ImmutableMap.of("id", "book-3",
                    "name", "Interview with the vampire",
                    "pageCount", "371",
                    "authorId", "author-3")
    );

    private static List<Map<String, String>> authors = Arrays.asList(
            ImmutableMap.of("id", "author-1",
                    "firstName", "Joanne",
                    "lastName", "Rowling"),
            ImmutableMap.of("id", "author-2",
                    "firstName", "Herman",
                    "lastName", "Melville"),
            ImmutableMap.of("id", "author-3",
                    "firstName", "Anne",
                    "lastName", "Rice")
    );

    public DataFetcher getBookByIdDataFetcher() {
        return dataFetchingEnvironment -> {
            String bookId = dataFetchingEnvironment.getArgument("id");
            return books
                    .stream()
                    .filter(book -> book.get("id").equals(bookId))
                    .findFirst()
                    .orElse(null);
        };
    }

    public DataFetcher getAuthorDataFetcher() {
        return dataFetchingEnvironment -> {
            Map<String,String> book = dataFetchingEnvironment.getSource();
            String authorId = book.get("authorId");
            return authors
                    .stream()
                    .filter(author -> author.get("id").equals(authorId))
                    .findFirst()
                    .orElse(null);
        };
    }
}

註:這種方式,並不要求一定要提供type對應的java class,只要在對應的DataFetcher中返回符合type的數據格式即可

三、方式三,不使用graphql-java-tools

不定義schema.graphqls,以編碼的方式創建graphql type。

定義graphql type:

GraphQLObjectType fooType = newObject()
        .name("Foo")
        .field(newFieldDefinition()
                .name("bar")
                .type(GraphQLString))
        .build();

上述代碼相當於使用schema方式創建了如下graphql type:

type Foo {
    bar: String
}

為類型的field指定DataFetcher:

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();

完整的代碼:

// 定義一個type Query 
/**
*  相當於 type QueryType{ hello: String }
*/
GraphQLObjectType queryType = newObject()
            .name("QueryType")
            .field(newFieldDefinition()
                    .name("hello")
                    .type(GraphQLString)
                    .dataFetcher(new StaticDataFetcher("world!"))
            .build();
// 創建GraphQLSchema
    GraphQLSchema schema = GraphQLSchema.newSchema()
            .query(queryType)
            .build();

    // Make the schema executable
    GraphQL executor = GraphQL.newGraphQL(graphQLSchema).build();
    ExecutionResult executionResult = executor.execute("{hello}");

四、參考鏈接

https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/

https://www.graphql-java-kickstart.com/tools/

https://www.graphql-java.com/documentation/v11/

記錄初學Spring boot中使用GraphQL編寫API的幾種方式