转载

一种灵活的API设计模式:在Spring Boot中支持GraphQL

导读:GraphQL是一种基于api的查询语言,它提供了一种更高效、强大和灵活的数据提供方式。它是由Facebook开发和开源,目前由来自世界各地的大公司和个人维护。本文作者先介绍了GraphQL,随后通过示例详细说明了GraphQL的开发流程是如何使用。

你可能已经听说过GraphQL以及Facebook如何在其移动应用中使用GraphQL。 在此本文中,我将向你展示如何在Spring Boot中组合GraphQL,看看GraphQL到底提供了什么样的功能。

为什么使用GraphQL?

GraphQL是REST API的查询语言。 GraphQL不受任何特定数据库或存储引擎的约束。 你现有的技术架构通常都支持GraphQL。

GraphQL的主要优势:

与REST不同,GraphQL无需在应用程序中创建多个API(应用程序编程接口)endpoint,在REST中,我们公开了多个endpoint以检索此类数据。

https://localhost:8080/person

https://localhost:8080/person/{id}

使用GraphQL,我们可以按需获取数据。 这与REST不同,在REST实现中,即使只需要一些属性的值,我们也会获取完整的数据响应。 例如,当我们查询REST API时,即使仅需要id和name,我们也会获得如下所示的完整数据。
{“id”: “100”,”name”: “Vijay”,”age”:34"city”: “Faridabad”,”gender”: “Male”}
通过REST API可以将前端(例如移动应用程序)与GraphQL集成在一起,并且响应非常迅速。

本文将介绍如何构建一个Spring Boot应用程序来存储和书查询籍信息。

创建应用

访问Spring Initializr或使用IntelliJ IDEA Ultimate生成具有Web,HSQLDB,Spring Boot 2.1.4等依赖项的Spring Boot应用程序。  

生成的POM如下。

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.1.4.RELEASE</version>

<relativePath/>

</parent>

<artifactId>springboot.graphql.app</artifactId>

<name>springboot-graphql-app</name>

<description>Demo project for Spring Boot with Graph QL</description>

<properties>

<java.version>1.8</java.version>

</properties>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.hsqldb</groupId>

<artifactId>hsqldb</artifactId>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>com.graphql-java</groupId>

<artifactId>graphql-spring-boot-starter</artifactId>

<version>3.6.0</version>

</dependency>

<dependency>

<groupId>com.graphql-java</groupId>

<artifactId>graphql-java-tools</artifactId>

<version>3.2.0</version>

</dependency>

</dependencies>

添加EndPoint

让我们从BookController开始,如下所示。

package graphqlapp.controller;

import graphqlapp.service.GraphQLService;

import graphql.ExecutionResult;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

@RequestMapping(“/rest/books”)

@RestController

public class BookController {

private static Logger logger = LoggerFactory.getLogger(BookController.class);

private GraphQLService graphQLService;

@Autowired

public BookController(GraphQLService graphQLService) {

this.graphQLService=graphQLService;

}

@PostMapping

public ResponseEntity<Object> getAllBooks(@RequestBody String query){

logger.info(“Entering getAllBooks@BookController”);

ExecutionResult execute = graphQLService.getGraphQL().execute(query);

return new ResponseEntity<>(execute, HttpStatus.OK);

}

}

添加model

接下来,我们将添加一个model类来代表书。 我们将其命名为Book。 model类的代码如下。

package graphqlapp.model;

import javax.persistence.Entity;

import javax.persistence.Id;

import javax.persistence.Table;

@Table

@Entity

public class Book {

@Id

private String isn;

private String title;

private String publisher;

private String publishedDate;

private String[] author;

public Book() {

}

public Book(String isn, String title, String publisher, String publishedDate, String[] author) {

this.isn = isn;

this.title = title;

this.publisher = publisher;

this.publishedDate = publishedDate;

this.author = author;

}

public String getIsn() {

return isn;

}

public void setIsn(String isn) {

this.isn = isn;

}

public String getTitle() {

return title;

}

public void setTitle(String title) {

this.title = title;

}

public String getPublisher() {

return publisher;

}

public void setPublisher(String publisher) {

this.publisher = publisher;

}

public String getPublishedDate() {

return publishedDate;

}

public void setPublishedDate(String publishedDate) {

this.publishedDate = publishedDate;

}

public String[] getAuthor() {

return author;

}

public void setAuthor(String[] author) {

this.author = author;

}

}

创建BookRepository

BookRepository扩展了JpaRepository,如下所示。

package graphqlapp.repository;

import graphqlapp.model.Book;

import org.springframework.data.jpa.repository.JpaRepository;

public interface BookRepository extends JpaRepository<Book, String> {

}

添加GraphQL模式(schema)

接下来,我们将在资源文件夹中编写一个GraphQL模式,名为books.graphql。

schema{

query:Query

}type Query{

allBooks: [Book]

book(id: String): Book

}type Book{

isn:String

title:String

publisher:String

author:[String]

publishedDate:String

}

该文件是使用GraphQL的关键。 在这里,我们定义了模式,你可以将其与查询相关联。 我们还定义了查询类型。

在此示例中,我们定义了两种类型:

  • 当用户查询所有书籍(通过使用allBooks)时,应用程序将返回一个Book数组。

  • 当用户通过传递ID查询特定书籍时,应用程序将返回Book对象。

添加GraphQL服务

接下来,我们需要添加GraphQL服务。 让我们将其命名为GraphQLService。

package graphqlapp.service;

import graphqlapp.model.Book;

import graphqlapp.repository.BookRepository;

import graphqlapp.service.datafetcher.AllBooksDataFetcher;

import graphqlapp.service.datafetcher.BookDataFetcher;

import graphql.GraphQL;

import graphql.schema.GraphQLSchema;

import graphql.schema.idl.RuntimeWiring;

import graphql.schema.idl.SchemaGenerator;

import graphql.schema.idl.SchemaParser;

import graphql.schema.idl.TypeDefinitionRegistry;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.core.io.Resource;

import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

import java.io.File;

import java.io.IOException;

import java.util.stream.Stream;

@Service

public class GraphQLService {

private static Logger logger = LoggerFactory.getLogger(GraphQLService.class);

private BookRepository bookRepository;

private AllBooksDataFetcher allBooksDataFetcher;

private BookDataFetcher bookDataFetcher;

@Value(“classpath:books.graphql”)

Resource resource;

private GraphQL graphQL;

@Autowired

public GraphQLService(BookRepository bookRepository, AllBooksDataFetcher allBooksDataFetcher,

BookDataFetcher bookDataFetcher) {

this.bookRepository=bookRepository;

this.allBooksDataFetcher=allBooksDataFetcher;

this.bookDataFetcher=bookDataFetcher;

}

@PostConstruct

private void loadSchema() throws IOException {

logger.info(“Entering loadSchema@GraphQLService”);

loadDataIntoHSQL();

//Get the graphql file

File file = resource.getFile();

//Parse SchemaF

TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(file);

RuntimeWiring runtimeWiring = buildRuntimeWiring();

GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);

graphQL = GraphQL.newGraphQL(graphQLSchema).build();

}

private void loadDataIntoHSQL() {

Stream.of(

new Book(“1001”, “The C Programming Language”, “PHI Learning”, “1978”,

new String[] {

“Brian W. Kernighan (Contributor)”,

“Dennis M. Ritchie”

}),

new Book(“1002”,”Your Guide To Scrivener”, “MakeUseOf.com”, “ April 21st 2013”,

new String[] {

“Nicole Dionisio (Goodreads Author)”

}),

new Book(“1003”,”Beyond the Inbox: The Power User Guide to Gmail”, “ Kindle Edition”, “November 19th 2012”,

new String[] {

“Shay Shaked”

, “Justin Pot”

, “Angela Randall (Goodreads Author)”

}),

new Book(“1004”,”Scratch 2.0 Programming”, “Smashwords Edition”, “February 5th 2015”,

new String[] {

“Denis Golikov (Goodreads Author)”

}),

new Book(“1005”,”Pro Git”, “by Apress (first published 2009)”, “2014”,

new String[] {

“Scott Chacon”

})

).forEach(book -> {

bookRepository.save(book);

});

}

private RuntimeWiring buildRuntimeWiring() {

return RuntimeWiring.newRuntimeWiring()

.type(“Query”, typeWiring -> typeWiring

.dataFetcher(“allBooks”, allBooksDataFetcher)

.dataFetcher(“book”, bookDataFetcher))

build();

}

public GraphQL getGraphQL(){

return graphQL;

}

}

当Spring Boot应用程序运行时,Spring框架将调用@PostConstruct方法。 @PostConstruct方法中的代码会将书籍信息写入HQL数据库中。

在此服务类的buildRuntimeWiring()方法中,我们将两个数据获取程序进行运行时绑定: allBook和book。 此处定义的名称allBookand book必须与我们已经创建的GraphQL文件中定义的类型匹配。

创建数据访问层

GraphQL模式中的每种类型都有一个对应的数据提取器(data fetcher)。

我们需要为在架构中定义的allBooks和Book类型编写两个单独的数据获取器。

allBooks类型的数据获取程器是这个。

package graphqlapp.service.datafetcher;

import graphql.schema.DataFetcher;

import graphql.schema.DataFetchingEnvironment;

import graphqlapp.model.Book;

import graphqlapp.repository.BookRepository;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import java.util.List;

@Component

public class AllBooksDataFetcher implements DataFetcher<List<Book>> {

private BookRepository bookRepository;

@Autowired

public AllBooksDataFetcher(BookRepository bookRepository) {

this.bookRepository=bookRepository;

}

@Override

public List<Book> get(DataFetchingEnvironment dataFetchingEnvironment) {

return bookRepository.findAll();

}

}

Book类型的数据获取器是这个。

package graphqlapp.service.datafetcher;

import graphql.schema.DataFetcher;

import graphqlapp.model.Book;

import graphqlapp.repository.BookRepository;

import graphql.schema.DataFetchingEnvironment;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

@Component

public class BookDataFetcher implements DataFetcher<Book> {

private BookRepository bookRepository;

@Autowired

public BookDataFetcher(BookRepository bookRepository){

this.bookRepository = bookRepository;

}

@Override

public Book get(DataFetchingEnvironment dataFetchingEnvironment) {

String isn = dataFetchingEnvironment.getArgument(“id”);

return bookRepository.findById(isn).orElse(null);

}

}

运行应用

我在端口9002端口上运行此应用程序。 因此,我在application.properties文件中如下。

server.port=9002

这样,我们的Spring Boot GraphQL应用程序就准备好了。 让我们运行我们的Spring Boot应用程序,并使用Postman工具对其进行测试。

注意这里我们只有一个endpoinst,

http://localhost:9002/rest/books


为此,请打开Postman并在请求正文中添加以下查询。

输入1: 我们要查询一本ID为1001的特定书,并且只需要书名即可。

同时,我们正在查询allBooks,并期望响应将包含is,title,author,publisher和publishedDate。

{

book(id:”1001"){

title

}

allBooks{

isn

title

author

publisher

publishedDate

}

}

输出1: 响应如下。

{

“errors”: [],

“data”: {

“book”: {

“title”: “The C Programming Language”

},

“allBooks”: [

{

“isn”: “1001”,

“title”: “The C Programming Language”,

“author”: [

“Brian W. Kernighan (Contributor)”,

“Dennis M. Ritchie”

],

“publisher”: “PHI Learning”,

“publishedDate”: “1978

},

{

“isn”: “1002”,

“title”: “Your Guide To Scrivener”,

“author”: [

Nicole Dionisio (Goodreads Author)”

],

“publisher”: “MakeUseOf.com”,

“publishedDate”: “ April 21st 2013”

},

{

“isn”: “1003”,

“title”: “Beyond the Inbox: The Power User Guide to Gmail”,

“author”: [

“Shay Shaked”,

“Justin Pot”,

Angela Randall (Goodreads Author)”

],

“publisher”: “ Kindle Edition”,

“publishedDate”: “November 19th 2012”

},

{

“isn”: “1004”,

“title”: “Scratch 2.0 Programming”,

“author”: [

Denis Golikov (Goodreads Author)”

],

“publisher”: “Smashwords Edition”,

“publishedDate”: “February 5th 2015”

},

{

“isn”: “1005”,

“title”: “Pro Git”,

“author”: [

“Scott Chacon”

],

“publisher”: “by Apress (first published 2009)”,

“publishedDate”: “2014”

}

]

},

“extensions”: null

}

输入2: 让我们再次通过ID查询特定图书的标题和作者。

{

book(id:”1001"){

title

author

}

}

输出2: 输出是这个。 我们获得ID为1001的书的标题和作者。

{

“errors”: [],

data”: {

“book”: {

“title”: “The C Programming Language”,

“author”: [

“Brian W. Kernighan (Contributor)”,

“Dennis M. Ritchie”

]

}

},

“extensions”: null

}

输入3: 让我们查询所有图书的书名,作者,出版日期和出版商详细信息

{

allBooks{

isn

title

author

publisher

publishedDate

}

}

输出3: 输出是这个。

{

“errors”: [],

“data”: {

“allBooks”: [

{

“isn”: “1001”,

“title”: “The C Programming Language”,

“author”: [

“Brian W. Kernighan (Contributor)”,

“Dennis M. Ritchie”

],

“publisher”: “PHI Learning”,

“publishedDate”: “1978

},

{

“isn”: “1002”,

“title”: “Your Guide To Scrivener”,

“author”: [

Nicole Dionisio (Goodreads Author)”

],

“publisher”: “MakeUseOf.com”,

“publishedDate”: “ April 21st 2013”

},

{

“isn”: “1003”,

“title”: “Beyond the Inbox: The Power User Guide to Gmail”,

“author”: [

“Shay Shaked”,

“Justin Pot”,

Angela Randall (Goodreads Author)”

],

“publisher”: “ Kindle Edition”,

“publishedDate”: “November 19th 2012”

},

{

“isn”: “1004”,

“title”: “Scratch 2.0 Programming”,

“author”: [

Denis Golikov (Goodreads Author)”

],

“publisher”: “Smashwords Edition”,

“publishedDate”: “February 5th 2015”

},

{

“isn”: “1005”,

“title”: “Pro Git”,

“author”: [

“Scott Chacon”

],

“publisher”: “by Apress (first published 2009)”,

“publishedDate”: “2014”

}

]

},

“extensions”: null

}

在REST API上使用GraphQL的优势就在于响应数据可以根据需求而变化,而不用返回一大堆无关的数据。

源代码:

https://github.com/vinod827/acloudtiger-blog-posts

原文地址:

https://medium.com/@vinod827/spring-boot-app-with-graphql-2dd62a9e5c3e

本文 由方圆翻译。转载本文请注明出处,欢迎更多小伙伴加入翻译及投稿文章的行列,详情请戳公众号菜单「联系我们」。

参考阅读:

  • 正式支持多线程!Redis 6.0与老版性能对比评测

  • 一百人研发团队的难题:研发管理、绩效考核、组织文化和OKR

  • 支付核心系统设计:Airbnb的分布式事务方案简介

  • 你真的了解压测吗?实战讲述性能测试场景设计和实现

  • 关于Golang GC的一些误解--真的比Java算法更领先吗?

  • Golang实现单机百万长连接服务 - 美图的三年优化经验

  • 一个Netflix开发的微服务编排引擎,支持可视化工作流定义

技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。转载请注明来自高可用架构「ArchNotes」微信公众号及包含以下二维码。

高可用架构

改变互联网的构建方式

一种灵活的API设计模式:在Spring Boot中支持GraphQL

长按二维码 关注「高可用架构」公众号

原文  http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=2653551512&idx=1&sn=25cf940f2ed9fde8ddd50d68d04ae91b
正文到此结束
Loading...