天天看點

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/

(此處已添加書籍卡片,請到今日頭條用戶端檢視)

繼續閱讀