一种灵活的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以检索此类数据。

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

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

创建应用

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

生成的POM如下。

<code><modelversion>4.0.0/<modelversion>/<code><code> <parent>/<code><code> <groupid>org.springframework.boot/<groupid>/<code><code> <artifactid>spring-boot-starter-parent/<artifactid>/<code><code> <version>2.1.4.RELEASE/<version>/<code><code> <relativepath>/<code><code> /<code><code><artifactid>springboot.graphql.app/<artifactid>/<code><code> <name>springboot-graphql-app/<name>/<code><code> <description>Demo project for Spring Boot with Graph QL/<description>/<code><code><properties>/<code><code> <java.version>1.8/<java.version>/<code><code> /<code><code><dependencies>/<code><code> <dependency>/<code><code> <groupid>org.springframework.boot/<groupid>/<code><code> <artifactid>spring-boot-starter-data-jpa/<artifactid>/<code><code> /<code><code> <dependency>/<code><code> <groupid>org.springframework.boot/<groupid>/<code><code> <artifactid>spring-boot-starter-web/<artifactid>/<code><code> /<code><code> <dependency>/<code><code> <groupid>org.hsqldb/<groupid>/<code><code> <artifactid>hsqldb/<artifactid>/<code><code> <scope>runtime/<scope>/<code><code> /<code><code> <dependency>/<code><code> <groupid>org.springframework.boot/<groupid>/<code><code> <artifactid>spring-boot-starter-test/<artifactid>/<code><code> <scope>test/<scope>/<code><code> /<code><code> <dependency>/<code><code> <groupid>com.graphql-java/<groupid>/<code><code> <artifactid>graphql-spring-boot-starter/<artifactid>/<code><code> <version>3.6.0/<version>/<code><code> /<code><code> <dependency>/<code><code> <groupid>com.graphql-java/<groupid>/<code><code> <artifactid>graphql-java-tools/<artifactid>/<code><code> <version>3.2.0/<version>/<code><code> /<code><code> /<code>

添加EndPoint

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

<code>package graphqlapp.controller;/<code><code>import graphqlapp.service.GraphQLService;/<code><code>import graphql.ExecutionResult;/<code><code>import org.slf4j.Logger;/<code><code>import org.slf4j.LoggerFactory;/<code><code>import org.springframework.beans.factory.annotation.Autowired;/<code><code>import org.springframework.http.HttpStatus;/<code><code>import org.springframework.http.ResponseEntity;/<code><code>import org.springframework.web.bind.annotation.PostMapping;/<code><code>import org.springframework.web.bind.annotation.RequestBody;/<code><code>import org.springframework.web.bind.annotation.RequestMapping;/<code><code>import org.springframework.web.bind.annotation.RestController;/<code><code>@RequestMapping(“/rest/books”)/<code><code>@RestController/<code><code>public class BookController {/<code><code> private static Logger logger = LoggerFactory.getLogger(BookController.class);/<code><code>private GraphQLService graphQLService;/<code><code>@Autowired/<code><code> public BookController(GraphQLService graphQLService) {/<code><code> this.graphQLService=graphQLService;/<code><code> }/<code><code>@PostMapping/<code><code> public ResponseEntity<object> getAllBooks(@RequestBody String query){/<object>/<code><code> logger.info(“Entering getAllBooks@BookController”);/<code><code> ExecutionResult execute = graphQLService.getGraphQL.execute(query);/<code><code> return new ResponseEntity<>(execute, HttpStatus.OK);/<code><code> }/<code><code>}/<code>

添加model

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

<code>package graphqlapp.model;/<code><code>import javax.persistence.Entity;/<code><code>import javax.persistence.Id;/<code><code>import javax.persistence.Table;/<code><code>@Table/<code><code>@Entity/<code><code>public class Book {/<code><code>@Id/<code><code> private String isn;/<code><code> private String title;/<code><code> private String publisher;/<code><code> private String publishedDate;/<code><code> private String author;/<code><code>public Book {/<code><code> }/<code><code>public Book(String isn, String title, String publisher, String publishedDate, String[] author) {/<code><code> this.isn = isn;/<code><code> this.title = title;/<code><code> this.publisher = publisher;/<code><code> this.publishedDate = publishedDate;/<code><code> this.author = author;/<code><code> }/<code><code>public String getIsn {/<code><code> return isn;/<code><code> }/<code><code>public void setIsn(String isn) {/<code><code> this.isn = isn;/<code><code> }/<code><code>public String getTitle {/<code><code> return title;/<code><code> }/<code><code>public void setTitle(String title) {/<code><code> this.title = title;/<code><code> }/<code><code>public String getPublisher {/<code><code> return publisher;/<code><code> }/<code><code>public void setPublisher(String publisher) {/<code><code> this.publisher = publisher;/<code><code> }/<code><code>public String getPublishedDate {/<code><code> return publishedDate;/<code><code> }/<code><code>public void setPublishedDate(String publishedDate) {/<code><code> this.publishedDate = publishedDate;/<code><code> }/<code><code>public String getAuthor {/<code><code> return author;/<code><code> }/<code><code>public void setAuthor(String[] author) {/<code><code> this.author = author;/<code><code> }/<code><code>}/<code>

创建BookRepository

BookRepository扩展了JpaRepository,如下所示。

<code>package graphqlapp.repository;/<code><code>import graphqlapp.model.Book;/<code><code>import org.springframework.data.jpa.repository.JpaRepository;/<code><code>public interface BookRepository extends JpaRepository<book> {/<book>/<code><code>}/<code>

添加GraphQL模式(schema)

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

<code>schema{/<code><code> query:Query/<code><code>}type Query{/<code><code> allBooks: [Book]/<code><code> book(id: String): Book/<code><code>}type Book{/<code><code> isn:String/<code><code> title:String/<code><code> publisher:String/<code><code> author:[String]/<code><code> publishedDate:String/<code><code>}/<code>

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

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

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

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

添加GraphQL服务

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

<code>package graphqlapp.service;/<code><code>import graphqlapp.model.Book;/<code><code>import graphqlapp.repository.BookRepository;/<code><code>import graphqlapp.service.datafetcher.AllBooksDataFetcher;/<code><code>import graphqlapp.service.datafetcher.BookDataFetcher;/<code><code>import graphql.GraphQL;/<code><code>import graphql.schema.GraphQLSchema;/<code><code>import graphql.schema.idl.RuntimeWiring;/<code><code>import graphql.schema.idl.SchemaGenerator;/<code><code>import graphql.schema.idl.SchemaParser;/<code><code>import graphql.schema.idl.TypeDefinitionRegistry;/<code><code>import org.slf4j.Logger;/<code><code>import org.slf4j.LoggerFactory;/<code><code>import org.springframework.beans.factory.annotation.Autowired;/<code><code>import org.springframework.beans.factory.annotation.Value;/<code><code>import org.springframework.core.io.Resource;/<code><code>import org.springframework.stereotype.Service;/<code><code>import javax.annotation.PostConstruct;/<code><code>import java.io.File;/<code><code>import java.io.IOException;/<code><code>import java.util.stream.Stream;/<code><code>@Service/<code><code>public class GraphQLService {/<code><code> private static Logger logger = LoggerFactory.getLogger(GraphQLService.class);/<code><code>private BookRepository bookRepository;/<code><code>private AllBooksDataFetcher allBooksDataFetcher;/<code><code>private BookDataFetcher bookDataFetcher;/<code><code>@Value(“classpath:books.graphql”)/<code><code> Resource resource;/<code><code>private GraphQL graphQL;/<code><code>@Autowired/<code><code> public GraphQLService(BookRepository bookRepository, AllBooksDataFetcher allBooksDataFetcher,/<code><code> BookDataFetcher bookDataFetcher) {/<code><code> this.bookRepository=bookRepository;/<code><code> this.allBooksDataFetcher=allBooksDataFetcher;/<code><code> this.bookDataFetcher=bookDataFetcher;/<code><code> }/<code><code>@PostConstruct/<code><code> private void loadSchema throws IOException {/<code><code> logger.info(“Entering loadSchema@GraphQLService”);/<code><code> loadDataIntoHSQL;/<code><code>//Get the graphql file/<code><code> File file = resource.getFile;/<code><code>//Parse SchemaF/<code><code> TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser.parse(file);/<code><code> RuntimeWiring runtimeWiring = buildRuntimeWiring;/<code><code> GraphQLSchema graphQLSchema = new SchemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);/<code><code> graphQL = GraphQL.newGraphQL(graphQLSchema).build;/<code><code> }/<code><code>private void loadDataIntoHSQL {/<code><code> Stream.of(/<code><code> new Book(“1001”, “The C Programming Language”, “PHI Learning”, “1978”,/<code><code> new String {/<code><code> “Brian W. Kernighan (Contributor)”,/<code><code> “Dennis M. Ritchie”/<code><code> }),/<code><code> new Book(“1002”,”Your Guide To Scrivener”, “MakeUseOf.com”, “ April 21st 2013”,/<code><code> new String {/<code><code> “Nicole Dionisio (Goodreads Author)”/<code><code> }),/<code><code> new Book(“1003”,”Beyond the Inbox: The Power User Guide to Gmail”, “ Kindle Edition”, “November 19th 2012”,/<code><code> new String {/<code><code> “Shay Shaked”/<code><code> , “Justin Pot”/<code><code> , “Angela Randall (Goodreads Author)”/<code><code> }),/<code><code> new Book(“1004”,”Scratch 2.0 Programming”, “Smashwords Edition”, “February 5th 2015”,/<code><code> new String {/<code><code> “Denis Golikov (Goodreads Author)”/<code><code> }),/<code><code> new Book(“1005”,”Pro Git”, “by Apress (first published 2009)”, “2014”,/<code><code> new String {/<code><code> “Scott Chacon”/<code><code> })/<code><code>).forEach(book -> {/<code><code> bookRepository.save(book);/<code><code> });/<code><code> }/<code><code>private RuntimeWiring buildRuntimeWiring {/<code><code> return RuntimeWiring.newRuntimeWiring/<code><code> .type(“Query”, typeWiring -> typeWiring/<code><code> .dataFetcher(“allBooks”, allBooksDataFetcher)/<code><code> .dataFetcher(“book”, bookDataFetcher))/<code><code> build;/<code><code> }/<code><code>public GraphQL getGraphQL{/<code><code> return graphQL;/<code><code> }/<code><code>}/<code>

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

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

创建数据访问层

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

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

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

<code>package graphqlapp.service.datafetcher;/<code><code>import graphql.schema.DataFetcher;/<code><code>import graphql.schema.DataFetchingEnvironment;/<code><code>import graphqlapp.model.Book;/<code><code>import graphqlapp.repository.BookRepository;/<code><code>import org.springframework.beans.factory.annotation.Autowired;/<code><code>import org.springframework.stereotype.Component;/<code><code>import java.util.List;/<code><code>@Component/<code><code>public class AllBooksDataFetcher implements DataFetcher<list>> {/<list>/<code><code>private BookRepository bookRepository;/<code><code>@Autowired/<code><code> public AllBooksDataFetcher(BookRepository bookRepository) {/<code><code> this.bookRepository=bookRepository;/<code><code> }/<code><code>@Override/<code><code> public List<book> get(DataFetchingEnvironment dataFetchingEnvironment) {/<book>/<code><code> return bookRepository.findAll;/<code><code> }/<code><code>}/<code>

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

<code>package graphqlapp.service.datafetcher;/<code><code>import graphql.schema.DataFetcher;/<code><code>import graphqlapp.model.Book;/<code><code>import graphqlapp.repository.BookRepository;/<code><code>import graphql.schema.DataFetchingEnvironment;/<code><code>import org.springframework.beans.factory.annotation.Autowired;/<code><code>import org.springframework.stereotype.Component;/<code><code>@Component/<code><code>public class BookDataFetcher implements DataFetcher<book> {/<book>/<code><code>private BookRepository bookRepository;/<code><code>@Autowired/<code><code> public BookDataFetcher(BookRepository bookRepository){/<code><code> this.bookRepository = bookRepository;/<code><code> }/<code><code>@Override/<code><code> public Book get(DataFetchingEnvironment dataFetchingEnvironment) {/<code><code> String isn = dataFetchingEnvironment.getArgument(“id”);/<code><code> return bookRepository.findById(isn).orElse;/<code><code> }/<code><code>}/<code>

运行应用

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

server.port=9002

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

注意这里我们只有一个endpoinst,http://localhost:9002/rest/books

让我们使用该单个endpoint查询多个数据集。 为此,请打开Postman并在请求正文中添加以下查询。

输入1:我们要查询一本ID为1001的特定书,并且只需要书名即可。 同时,我们正在查询allBooks,并期望响应将包含is,title,author,publisher和publishedDate。

<code>{/<code><code> book(id:”1001"){/<code><code> title /<code><code> }/<code><code> allBooks{/<code><code> isn/<code><code> title/<code><code> author/<code><code> publisher/<code><code> publishedDate/<code><code> }/<code><code>}/<code>

输出1:响应如下。

<code>{/<code><code> “errors”: ,/<code><code> “data”: {/<code><code> “book”: {/<code><code> “title”: “The C Programming Language”/<code><code> },/<code><code> “allBooks”: [/<code><code> {/<code><code> “isn”: “1001”,/<code><code> “title”: “The C Programming Language”,/<code><code> “author”: [/<code><code> “Brian W. Kernighan (Contributor)”,/<code><code> “Dennis M. Ritchie”/<code><code> ],/<code><code> “publisher”: “PHI Learning”,/<code><code> “publishedDate”: “1978”/<code><code> },/<code><code> {/<code><code> “isn”: “1002”,/<code><code> “title”: “Your Guide To Scrivener”,/<code><code> “author”: [/<code><code> “Nicole Dionisio (Goodreads Author)”/<code><code> ],/<code><code> “publisher”: “MakeUseOf.com”,/<code><code> “publishedDate”: “ April 21st 2013”/<code><code> },/<code><code> {/<code><code> “isn”: “1003”,/<code><code> “title”: “Beyond the Inbox: The Power User Guide to Gmail”,/<code><code> “author”: [/<code><code> “Shay Shaked”,/<code><code> “Justin Pot”,/<code><code> “Angela Randall (Goodreads Author)”/<code><code> ],/<code><code> “publisher”: “ Kindle Edition”,/<code><code> “publishedDate”: “November 19th 2012”/<code><code> },/<code><code> {/<code><code> “isn”: “1004”,/<code><code> “title”: “Scratch 2.0 Programming”,/<code><code> “author”: [/<code><code> “Denis Golikov (Goodreads Author)”/<code><code> ],/<code><code> “publisher”: “Smashwords Edition”,/<code><code> “publishedDate”: “February 5th 2015”/<code><code> },/<code><code> {/<code><code> “isn”: “1005”,/<code><code> “title”: “Pro Git”,/<code><code> “author”: [/<code><code> “Scott Chacon”/<code><code> ],/<code><code> “publisher”: “by Apress (first published 2009)”,/<code><code> “publishedDate”: “2014”/<code><code> }/<code><code> ]/<code><code> },/<code><code> “extensions”: /<code><code>}/<code>

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

<code>{/<code><code> book(id:”1001"){/<code><code> title/<code><code> author/<code><code> }/<code><code>}/<code>

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

<code>{/<code><code> “errors”: ,/<code><code> “data”: {/<code><code> “book”: {/<code><code> “title”: “The C Programming Language”,/<code><code> “author”: [/<code><code> “Brian W. Kernighan (Contributor)”,/<code><code> “Dennis M. Ritchie”/<code><code> ]/<code><code> }/<code><code> },/<code><code> “extensions”: /<code><code>}/<code>

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

<code>{/<code><code> allBooks{/<code><code> isn/<code><code> title/<code><code> author/<code><code> publisher/<code><code> publishedDate/<code><code> }/<code><code>}/<code>

输出3:输出是这个。

<code>{/<code><code> “errors”: ,/<code><code> “data”: {/<code><code> “allBooks”: [/<code><code> {/<code><code> “isn”: “1001”,/<code><code> “title”: “The C Programming Language”,/<code><code> “author”: [/<code><code> “Brian W. Kernighan (Contributor)”,/<code><code> “Dennis M. Ritchie”/<code><code> ],/<code><code> “publisher”: “PHI Learning”,/<code><code> “publishedDate”: “1978”/<code><code> },/<code><code> {/<code><code> “isn”: “1002”,/<code><code> “title”: “Your Guide To Scrivener”,/<code><code> “author”: [/<code><code> “Nicole Dionisio (Goodreads Author)”/<code><code> ],/<code><code> “publisher”: “MakeUseOf 
.com”,/<code><code> “publishedDate”: “ April 21st 2013”/<code><code> },/<code><code> {/<code><code> “isn”: “1003”,/<code><code> “title”: “Beyond the Inbox: The Power User Guide to Gmail”,/<code><code> “author”: [/<code><code> “Shay Shaked”,/<code><code> “Justin Pot”,/<code><code> “Angela Randall (Goodreads Author)”/<code><code> ],/<code><code> “publisher”: “ Kindle Edition”,/<code><code> “publishedDate”: “November 19th 2012”/<code><code> },/<code><code> {/<code><code> “isn”: “1004”,/<code><code> “title”: “Scratch 2.0 Programming”,/<code><code> “author”: [/<code><code> “Denis Golikov (Goodreads Author)”/<code><code> ],/<code><code> “publisher”: “Smashwords Edition”,/<code><code> “publishedDate”: “February 5th 2015”/<code><code> },/<code><code> {/<code><code> “isn”: “1005”,/<code><code> “title”: “Pro Git”,/<code><code> “author”: [/<code><code> “Scott Chacon”/<code><code> ],/<code><code> “publisher”: “by Apress (first published 2009)”,/<code><code> “publishedDate”: “2014”/<code><code> }/<code><code> ]/<code><code> },/<code><code> “extensions”: /<code><code>}/<code>

在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开发的微服务编排引擎,支持可视化工作流定义

高可用架构

改变互联网的构建方式


分享到:


相關文章: