天天看点

Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API

作者:爱科学的卫斯理

在我们使用REST API的时候,我们是不是经常遇到这样一些问题?

1、为了适配不同端(手机、Web等)的请求,我们不得不开发多个接口来适配不同端的数据要求。 是不是更有甚者,通过一个接口把能给的全部数据都给前端,让前端自己选择?

2、因为REST API无状态(没有客户端信息存储在请求之间,每一个请求都是分开和独立的)的特性,前端为了完成一个功能不要多次调用不同的接口查询,从而不得不需要一个接口聚合的功能。

现在GraphQL来了!

Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API

1、什么是GraphQL

GraphQL是在2012年由Facebook创造,并在2015开源,现在由一个中立的组织GraphQL Foundation管理。几乎所有的语言都有GraphQL规范的实现。

GraphQL的Graph意为用类“图”的形式来描述数据结构。而QL的含义为查询语言,意味着是客户端查询API的方式。

更多了解GraphQL规范可参考:https://spec.graphql.org/

2、Spring for GraphQL与Spring Boot 2.7

对于Java程序员,学习一门新技术最轻松也是最快速的方式是等Spring对某项技术进行集成。

Spring于2022.5.18发布了“Spring for GraphQL v1.0.0”版本,并在2022.5.19日发布Spring Boot 2.7,对“Spring for GraphQL”进行自动配置和指标支持。

Spring for GraphQL基于GraphQL Java(https://github.com/graphql-java/graphql-java),在Spring Boot下使用GraphQL至少需要“spring-boot-starter-graphql”这个starter。

因GraphQL是和传输协议无关的,这就意味着你可以在Spring Web、Webflux、Websocket、RSocket这些传输协议中使用GraphQL。

更多了解Spring for GraphQL可参考:https://docs.spring.io/spring-graphql/docs/current/reference/html/

更多了解Spring Boot 2.7的GraphQL相关信息可参考:https://docs.spring.io/spring-boot/docs/current/reference/html/web.html#web.graphql

3、Spring Boot 2.7 下的 GraphQL实战

3.1 生成演示项目

添加依赖“Spring for GraphQL”、“Spring Web”,“Websocket”(订阅需要此依赖)。

Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API

GraphQL本身和数据库技术是没有关系的,在此处我们为演示方便,使用“Spring Data JPA”和“H2”数据库用来存储和查询数据。

3.2 简单的演示数据实体模型

我们定义两个简单的JPA实体,一个为“书”(Book),一个为“作者”(Author),每本书包含一个作者。

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    @Id
    @GeneratedValue
    private Long id;
    @Column(unique = true)
    private String isbn;
    private String title;
    private Integer pages;
    private String authorIdCardNo;
}           

上面在Book类里只是引用了authorIdCardNo,并没有映射Author对象,这里其实是DDD(领域驱动设计)推荐的最佳实践,很多人不愿意学JPA(Hibernate)的很多原因都是因为很陡峭的学习曲线,被一些对象之间的关系(一对多、多对多等)搞的糊里糊涂的。

在DDD里,推荐类对象之间不要有硬性的依赖关系(表之间没有外键关系),通过业务ID去互相进行低耦合的关联,业务ID可能是“身份证号码”、“手机号码”或者“订单号”一类的业务唯一标识。

在本例中,Book的业务ID是ISBN号,Author的业务ID是身份证号码。这样Book和Author无论是对象关系还是表关系都没有依赖,保持了松耦合。

在使用DDD设计实体的时候,类对象之间是很少有关系的!

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Author {
    @Id
    @GeneratedValue
    private Long id;
    @Column(unique = true)
    private String idCardNo;
    private String name;
    private Integer age;

}           

3.3 准备数据访问和演示数据

两个简单的数据访问接口:

public interface BookRepository extends JpaRepository<Book,Long> {
    Book findByIsbn(String isbn);
}           
public interface BookRepository extends JpaRepository<Book,Long> {
    Book findByIsbn(String isbn);
}           

让程序启动时自动创建Book和Author的表,关闭时自动删除表:

spring.jpa.hibernate.ddl-auto=create-drop           

准备工作做好,我们开始使用Spring Boot 2.7进行GraphQL的实战。

3.2 GraphQL的Schema的基础知识

GraphQL的schema是GraphQL服务器用来描述GraphQL API的。这个schema包含三种类型。

  • 对象类型(Object Type)

对象类型对应着我们上面定义的Book和Author实体模型,当然这不是严格对应的,我们可以在GraphQL的对象里定义更多的或者更少的属性。

  • 标量类型(Scalar Type)

GraphQL内置了一些标量类型:

1、Int:有符32位整数

2、Float:有符双精度浮点类型

3、String:UTF-8字符序列

4、ID:代表唯一标识,一般用来获取对象

  • 根操作类型(Root Operation Type)

根操作类型用来定义如何通过API读写数据。在GraphQL中有三种根操作类型:

1、Query:用来读取数据

2、Mutation:用来创建、更新和删除数据

3、Subscription:类似于Query,用来查询数据;Subscription(订阅)提供一种长时间的操作,当时间改变时数据结果也会有实时变化。

3.3 在Spring Boot中定义GraphQL Schema

Spring Boot会自动在src/main/resources/graphql/** 目录寻找结尾为".graphqls" or ".gqls" 的schema文件,我们可以使用spring.graphql.schema.locations 自定义文件的位置,或使用spring.graphql.schema.file-extensions 定制文件的扩展名。

schema.graphqls :我们也可以按照类型将他们分散在多个文件中

# 对象类型
type Book {
    isbn: ID!
    title: String!
    pages: Int
    author: Author!
}

type Author {
    idCardNo: ID!
    name: String!
    age: Int
}

#根操作查询数据
type Query {
    allBooks: [Book]!
    bookByIsbn(isbn: ID): Book

}

# 根操作创建数据
type Mutation {
    createBook(bookInput: BookInput): Book
    createAuthor(authorInput: AuthorInput): Author
}

input BookInput { # 定义方法的入参
    isbn: ID!
    title: String!
    pages: Int
    authorIdCardNo: String
}

input AuthorInput { # 定义方法的入参
    idCardNo: ID!
    name: String!
    age: Int
}

# 根操作订阅数据
type Subscription {
    greetings: String
}
           

3.4 GraphQL的控制器

我们只需要使用常规的“@Controller”注解就可以将类标注成控制器,通过Spring for GraphQL的“@QueryMapping”、“@MutationMapping”和“@SubscriptionMapping”注解来标注根操作。

和Spring MVC的@RequestMapping类似,Spring for GraphQL也提供了一个“@SchemaMapping”,通过配置不同的参数,从而达到“@QueryMapping”、“@MutationMapping”和“@SubscriptionMapping”一样的效果,但是我们还是会使用特定的注解而不是通用的注解。

  • Book的控制器
@Controller
public class BookController {

    private final BookRepository bookRepository;
    private final AuthorRepository authorRepository;

    public BookController(BookRepository bookRepository, AuthorRepository authorRepository) {
        this.bookRepository = bookRepository;
        this.authorRepository = authorRepository;
    }

    @QueryMapping
    public List<Book> allBooks(){
        return bookRepository.findAll();
    }

    @QueryMapping
    public Book bookByIsbn(@Argument String isbn){
        return bookRepository.findByIsbn(isbn);
    }

    @SchemaMapping(typeName = "Book" ,field = "author") 
    public Author getAuthor(Book book){
        return authorRepository.findByIdCardNo(book.getAuthorIdCardNo());
    }

    @MutationMapping
    public Book createBook(@Argument BookInput bookInput){
        Book book = new Book();
        BeanUtils.copyProperties(bookInput,book);
        return bookRepository.save(book);
    }

}           

特别注意代码

@SchemaMapping(typeName = "Book" ,field = "author") 
    public Author getAuthor(Book book){
        return authorRepository.findByIdCardNo(book.getAuthorIdCardNo());
    }           

这里是为了schema文件中,类型Book的author属性来获取值的,通过“@SchemaMapping”注解来设置“typeName”和“field”来实现,通过这种方式我们可以把级联多个级别的查询聚合在一起。

新增Book所需的BookInput类很简单:

@Data
public class BookInput {
    private String isbn;
    private String title;
    private Integer pages;
    private String authorIdCardNo;
}
           
  • Author控制器
@Controller
public class AuthorController {
    private final AuthorRepository authorRepository;

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

    @MutationMapping
    public Author createAuthor(@Argument AuthorInput authorInput){
        Author author = new Author();
        BeanUtils.copyProperties(authorInput,author);
        return authorRepository.save(author);
    }
}
           

新增Author所用的AuthorInput类也很简单:

@Data
public class AuthorInput {
    private String idCardNo;
    private String name;
    private Integer age;
}           
  • Greeting控制器

这个控制器是为了演示订阅的,订阅需要有websocket的支持。

@Controller
public class GreetingController {
    @SubscriptionMapping
    public Flux<String> greetings(){
        return Flux.fromStream(Stream.generate(() -> new String("Hello GraphQL " + UUID.randomUUID())))
                .delayElements(Duration.ofSeconds(5))
                .take(10);
    }

}           

这里的处理是,每五秒向客户端发送“Hello GraphQL”消息,发送10次。

我们还需指定websocket的端点路径,此时application.properties内容如下:

spring.jpa.hibernate.ddl-auto=create-drop
spring.graphql.websocket.path=/graphql           

3.5 使用GraphiQL客户端工具测试

Spring Boot内嵌了一个GraphQL的可视化界面工具GraphiQL来编写和查询数据,需要在application.properties中开启:

spring.jpa.hibernate.ddl-auto=create-drop
spring.graphql.websocket.path=/graphql
spring.graphql.graphiql.enabled=true           

启动程序,访问:http://localhost:8080/graphiql

Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API
  • 首先演示订阅功能:

语句为:

subscription {
  greetings
}           
Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API
Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API

你可以看到订阅的数据每五秒钟发生了实时的变化。

  • 新增Author语句
mutation{
  createAuthor(authorInput: {
    idCardNo: "341234567891234567",
    name: "汪云飞",
    age: 38
  }
  ) {
    name
    age
  }
}           
mutation{
  createAuthor(authorInput: {
    idCardNo: "341234567891234568",
    name: "罗伯特C.马丁",
    age: 70
  }
  ) {
    name
    age
  }
}           
Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API
  • 新增Book语句
mutation{
  createBook(bookInput: {
    isbn: "9787121282089",
    title: "JavaEE开发的颠覆者:Spring Boot实战",
    pages: 524,
    authorIdCardNo: "341234567891234567"
  }
  ) {
    title
    pages
  }
}
           
mutation{
  createBook(bookInput: {
    isbn: "9787121377921",
    title: "从企业级开发到云原生微服务:Spring Boot实战",
    pages: 504,
    authorIdCardNo: "341234567891234567"
  }
  ) {
    title
    pages
  }
}
           
mutation{
  createBook(bookInput: {
    isbn: "9787121347962",
    title: "架构整洁之道",
    pages: 348,
    authorIdCardNo: "341234567891234568"
  }
  ) {
    title
    pages
  }
}           
Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API
  • 查询Book语句

我们可以指定不同的属性,获得不同的查询结果。

语句为:

query{
  allBooks {
    title
    author {
      name
    }
  }
}           

结果为:

Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API

语句为:

query{
  allBooks {
    isbn
    title
    author {
      name
      age
    }
  }
}           

查询结果为:

Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API

通过isbn号查询,语句为:

query{
    bookByIsbn(isbn: "9787121377921"){
        isbn
        title
        author {
          name
          age
        }
    }
}           

结果为:

Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API

3.6 使用Postman进行测试

postman支持GraphQL协议,路径填写GraphQL的端点:http://localhost:8080/graphql

在Body中有GraphQL的选项。

Spring Boot 2.7最大新特性:GraphQL实战!拯救REST API

3.7 前端的支持

本例显示的是GraphQL的服务端功能,现在GraphQL客户端的支持也很多。

Vue Apollo:https://apollo.vuejs.org/

React/ios/kotlin Apollo Client:https://www.apollographql.com/

Apollo Angular:https://www.the-guild.dev/graphql/apollo-angular

4 结语

如果你对你现在的REST API有些不满意,或者和你配合的前端开发对REST API不满意,在Spring Boot 2.7的加持下,我们可以快速简单的使用GraphQL辅助或替代REST帮助我们进行前后端的接口配合开发。

本文源码地址:https://github.com/wiselyman/graphql-demo

感谢对我的书《从企业级开发到云原生微服务:Spring Boot实战》的支持。

转载请注明出处:今日头条:爱科学的卫斯理

参考资料:

https://tanzu.vmware.com/developer/guides/spring-for-graphql/

https://blog.devgenius.io/graphql-with-java-and-spring-boot-b64d3ce427a2

https://www.baeldung.com/spring-graphql

https://blog.devgenius.io/graphql-with-spring-boot-starter-graphql-7b406998c0b5

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

https://www.danvega.dev/blog/2022/05/17/spring-for-graphql/

(此处已添加书籍卡片,请到今日头条客户端查看)

继续阅读