記錄初學Spring boot中使用GraphQL編寫API的幾種方式
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字段順序如下:
- method
(*fieldArgs [, DataFetchingEnvironment]) - method is
(*fieldArgs [, DataFetchingEnvironment]), only if the field returns a Boolean - method get
(*fieldArgs [, DataFetchingEnvironment]) - method getField
(*fieldArgs [, DataFetchingEnvironment]) - field
例如:
上述type Book中的name字段的映射順序為:
- 在java Book類中找name(參數)方法。
- 如果Book類中沒有name(參數)方法,則繼續找isName(參數)方法。
- 如果Book中沒有isName(參數)方法,則繼續在Book中找getName(參數)方法。
- 如果Book中沒有getName()方法,則繼續在Book中找getFieldName(參數)方法。
- 如果Book中沒有getFieldName(參數)方法,在繼續在Book中找name成員變量。
- 如果Book中沒有name成員變量,則報錯。
graphql type中的字段映射Resolver的優先級:
- method
(dataClassInstance, *fieldArgs [, DataFetchingEnvironment]) - method is
(dataClassInstance, *fieldArgs [, DataFetchingEnvironment]), only if the field returns a Boolean - method get
(dataClassInstance, *fieldArgs [, DataFetchingEnvironment]) - 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的幾種方式