1. 概述
在多年來,REST 已經成為設計 Web API 的事實標準架構風格。然而,GraphQL 和 gRPC 近來出現,旨在解決 REST 的一些侷限性。每種 API 方式都具有顯著的優勢和一些權衡。
在本教程中,我們首先將研究每種 API 設計方法。然後,我們將使用 Spring Boot 構建一個簡單的服務,採用這三種不同的方法。接下來,我們將通過研究在決定採用哪一種方法之前應考慮的多個標準來比較它們。
最後,由於沒有一種方法適用於所有情況,我們將看到不同的方法可以在不同的應用程序層之間混合使用。
2. REST
Representational State Transfer (REST) 是全球範圍內最常用的 API 架構風格。它由 Roy Fielding 於 2000 年定義。
2.1. 架構風格
REST 不是一個框架或庫,而是一種 基於 URL 結構和 HTTP 協議的接口描述的架構風格。它描述了一種適用於客户端與服務器交互的狀態無感知的、可緩存的、基於規範的架構。它使用 URL 來標識資源,並使用 HTTP 方法來表達要執行的操作:
- GET 用於獲取現有資源或多個資源
- POST 用於創建新的資源
- PUT 用於更新資源或如果不存在則創建它
- DELETE 用於刪除資源
- PATCH 用於部分更新現有資源
REST 可以用多種編程語言實現,並支持 JSON 和 XML 等多種數據格式。
2.2. 示例服務
我們可以使用 Spring 構建 REST 服務,通過 使用 @RestController 註解定義控制器類。接下來,我們定義與 HTTP 方法對應的函數,例如 GET,通過 @GetMapping 註解。最後,在註解參數中,我們提供方法應被觸發的資源路徑:
@GetMapping("/rest/books")
public List<Book> books() {
return booksService.getBooks();
}
MockMvc 提供 Spring 中 REST 服務集成測試的支持。它封裝了所有 Web 應用程序 Bean 並使它們可用於測試:
this.mockMvc.perform(get("/rest/books"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().json(expectedJson));
由於它們基於 HTTP,因此 REST 服務可以像在瀏覽器或使用 Postman 或 CURL 這樣的工具一樣進行測試:
$ curl http://localhost:8082/rest/books
2.3. 優點和缺點
REST 的最大優勢是它是 技術世界中最成熟的 API 架構風格。由於其受歡迎程度,許多開發人員已經熟悉 REST 並發現它易於使用。然而,由於其靈活性,REST 可以被不同的開發人員以不同的方式解釋。
由於每個資源通常位於唯一的 URL 後面,因此可以輕鬆地監控和限制 API 速率。REST 還可以簡化緩存,通過利用 HTTP 實現。通過緩存 HTTP 響應,我們的客户端和服務器不需要不斷與彼此交互。
REST 容易出現下取數和上取數的問題。例如,要獲取嵌套實體,可能需要進行多個請求。另一方面,在 REST API 中,通常不可能僅獲取特定實體數據的片段。客户端始終接收請求端點配置為返回的所有數據。
3. GraphQL
GraphQL 是 Facebook 開發的開源 API 查詢語言。
3.1. 架構風格
GraphQL 提供了一種 查詢語言,用於開發 API,並提供用於滿足這些查詢的框架。它不依賴 HTTP 方法來操作數據,主要使用 POST 請求。與 GraphQL 相比,GraphQL 使用查詢、修改和訂閲:
- 查詢用於從服務器請求數據
- 修改用於在服務器上修改數據
- 訂閲用於在數據更改時獲取實時更新
GraphQL 是客户端驅動的,因為它允許客户端定義其特定用例所需的數據。請求的數據隨後在一個往返中從服務器檢索。
3.2. 示例服務
在 GraphQL 中,數據由方案定義,方案定義對象、字段和類型。因此,我們將首先定義 GraphQL 方案,用於我們的示例服務:
type Author {
firstName: String!
lastName: String!
}
type Book {
title: String!
year: Int!
author: Author!
}
type Query {
books: [Book]
}
我們可以使用 Spring 類似 REST 服務的方式構建 GraphQL 服務,通過使用 @RestController 類註解。接下來,我們使用 @QueryMapping 註解標記該函數為 GraphQL 數據獲取組件:
@QueryMapping
public List<Book> books() {
return booksService.getBooks();
}
HttpGraphQlTester 提供了 Spring 中 GraphQL 服務集成測試的支持。它封裝了所有 Web 應用程序 Bean 並使它們可供測試:
this.graphQlTester.document(document)
.execute()
.path("books")
.matchesJson(expectedJson);
可以使用 Postman 或 CURL 測試 GraphQL 服務。但是,它們需要指定在 POST 請求正文中查詢:
$ curl -X POST -H "Content-Type: application/json" -d "{\"query\":\"query{books{title}}\"}" http://localhost:8082/graphql
3.3. 優點和缺點
GraphQL 對客户端非常靈活,因為它 允許僅檢索和傳遞請求的數據。由於沒有發送過多的數據到網絡上,因此 GraphQL 可以帶來更好的性能。
它使用比 REST 更加嚴格的規範,與 REST 的模糊性相比。此外,GraphQL 提供了詳細的錯誤描述,用於調試目的,並自動生成 API 更改的文檔。
由於每個查詢可以不同,GraphQL 會破壞中間代理緩存,使得緩存實現更加困難。此外,由於 GraphQL 查詢可能會執行大型和複雜的服務器端操作,因此查詢通常會受到複雜性的限制,以避免過載服務器。
4. gRPC
RPC 代表遠程程序調用,gRPC 是 Google 創建的高性能、開源 RPC 框架。
4.1. 架構風格
gRPC 框架基於遠程程序調用的客户端-服務器模型。 客户端應用程序可以直接調用服務器應用程序的方法, 就像它是一個本地對象一樣。 這是一個基於合同的嚴格方法,客户端和服務器都需要訪問相同的模式定義。
在 gRPC 中,一種稱為協議緩衝區語言的 DSL 定義請求和響應類型。 協議緩衝區編譯器然後生成服務器和客户端代碼工件。 我們可以擴展生成的服務器代碼,添加自定義業務邏輯,並提供響應數據。
該框架支持多種類型的客户端-服務器交互:
- 傳統的請求-響應交互
- 服務器端流式傳輸,一個客户端請求可以產生多個響應
- 客户端流式傳輸,多個客户端請求產生一個響應
客户端和服務器通過 HTTP/2 使用緊湊的二進制格式進行通信,這使得 gRPC 消息的編碼和解碼非常高效。
4.2. 示例服務
類似於 GraphQL,我們首先定義一個模式,該模式定義服務、請求和響應,包括它們的字段和類型:
message BooksRequest {}
message AuthorProto {
string firstName = 1;
string lastName = 2;
}
message BookProto {
string title = 1;
AuthorProto author = 2;
int32 year = 3;
}
message BooksResponse {
repeated BookProto book = 1;
}
service BooksService {
rpc books(BooksRequest) returns (BooksResponse);
}
然後,我們需要將協議緩衝區文件傳遞給協議緩衝區編譯器,以生成所需的代碼。 我們可以手動使用預編譯的二進制文件執行此操作,或者使用 protobuf-maven-plugin 通過 Maven 構建過程來完成此操作。
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-plugin.version}</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
現在,我們可以擴展生成的 BooksServiceImplBase 類,用 @GrpcService 註解進行標註,並覆蓋 books 方法:
@Override
public void books(BooksRequest request, StreamObserver<BooksResponse> responseObserver) {
List<Book> books = booksService.getBooks();
BooksResponse.Builder responseBuilder = BooksResponse.newBuilder();
books.forEach(book -> responseBuilder.addBook(GrpcBooksMapper.mapBookToProto(book)));
responseObserver.onNext(responseBuilder.build());
responseObserver.onCompleted();
}
在 Spring 中測試 gRPC 服務是可能的,但目前不如 REST 和 GraphQL 成熟。 它的數據格式對人類來説不可讀,因此需要額外的工具來分析有效負載並進行調試。 此外,HTTP/2 僅通過 TLS 在現代瀏覽器中的最新版本中支持。
BooksRequest request = BooksRequest.newBuilder().build();
BooksResponse response = booksServiceGrpc.books(request);
List<Book> books = response.getBookList().stream()
.map(GrpcBooksMapper::mapProtoToBook)
.collect(Collectors.toList());
JSONAssert.assertEquals(objectMapper.writeValueAsString(books), expectedJson, true);
為了使此測試工作,我們需要用以下註解標註我們的測試類:
- @SpringBootTest,用於配置客户端連接到所謂的“內聯”測試服務器
- @SpringJUnitConfig,用於準備和提供應用程序的 Bean
- @DirtiesContext,以確保服務器在每次測試後正確關閉
Postman 最近添加了對 gRPC 服務的測試支持。 類似於 CURL,一個命令行工具 grpcurl 允許我們與 gRPC 服務器交互:
$ grpcurl --plaintext localhost:9090 com.baeldung.chooseapi.BooksService/books
該工具使用 JSON 編碼來使協議緩衝區編碼對人類更友好,用於測試目的。
4.3. 優點和缺點
gRPC 的最大優勢是性能,這得益於緊湊的數據格式、快速的消息編碼和解碼以及 HTTP/2 的使用。 此外,其代碼生成功能支持多種編程語言,並能幫助我們節省編寫樣板代碼的時間。
通過要求使用 HTTP 2 和 TLS/SSL,gRPC 提供了更好的安全默認設置和內置的流式傳輸支持。 語言無關的接口定義使不同編程語言編寫的服務之間進行通信成為可能。
然而,目前 gRPC 在開發者社區中的受歡迎程度不如 REST。 它的數據格式對人類來説不可讀,因此需要額外的工具來分析有效負載並進行調試。 此外,HTTP/2 僅通過 TLS 在現代瀏覽器中的最新版本中支持。
5. Which API to Choose
Now that we are familiar with all three API design approaches, let’s look at several criteria we should consider before deciding on one.
5.1. Data Format
REST is the most flexible approach in terms of request and response data formats. We can implement REST services to support one or multiple data formats like JSON and XML.
On the other hand, GraphQL defines its own query language that needs to be used when requesting data. GraphQL services respond in JSON format. Although it is possible to convert the response into another format, it’s uncommon and could affect performance.
The gRPC framework uses protocol buffers, a custom binary format. It is unreadable to humans, but it is also one of the main reasons which make gRPC so performant. Although supported in several programming languages, the format cannot be customized.
5.2. Data Fetch
GraphQL is the most efficient API approach for fetching data from the server. As it allows clients to select which data to fetch, there is usually no extra data sent over the network.
Both REST and gRPC do not support such advanced client querying. Thus, extra data might be returned by the server unless new endpoints or filters are developed and deployed on the server.
5.3. Browser Support
REST and GraphQL APIs are supported in all modern browsers. Typically, JavaScript client code is used to send over HTTP requests from the browser to the server APIs.
Browser support doesn’t come out of the box for gRPC APIs. However, an extension of gRPC for the web is available. It is based on HTTP 1.1., but doesn’t provide all gRPC features. Similar to a Java client, gRPC for the web requires the browser client code to generate a gRPC client from a protocol buffer schema.
5.4. Code Generation
GraphQL requires additional libraries to be added to a core framework like Spring. Those libraries add support for the processing of GraphQL schemas, annotation-based programming, and server handling of GraphQL requests. Code generation from a GraphQL schema is possible but not required. Any custom POJO matching a GraphQL type defined in the schema can be used.
The gRPC framework also requires additional libraries to be added to a core framework, as well as a mandatory code generation step. The protocol buffer compiler generates the server and client boilerplate code that we can then extend. In case we use custom POJOs, they will need to be mapped to the autogenerated protocol buffer types.
REST is an architectural style that can be implemented using any programming language and various HTTP libraries. It uses no predefined schema and does not require any code generation. That said, making use of Swagger or OpenAPI allows us to define a schema and generate code if we wish.
5.5. Response Time
Thanks to its optimized binary format,gRPC has significantly faster response times compared to REST and GraphQL. In addition, load balancing can be used in all three approaches to evenly distribute client requests across multiple servers.
However, in addition, gRPC makes use of HTTP 2.0 by default, which makes latency in gRPC lower than in REST and GraphQL APIs. With HTTP 2.0, several clients can send multiple requests simultaneously without establishing a new TCP connection. Most performance tests report that gRPC is roughly 5 to even 10 times faster than REST.
5.6. Caching
Caching requests and responses with REST is simple and mature as it allows for caching of data on the HTTP level. Each GET request exposes the application resources, which are easily cacheable by the browser, proxy servers, or CDNs.
Since GraphQL uses the POST method by default and each query can be different, it makes caching implementation more difficult. This is especially true when the client and the server are geographically distant from each other. A possible workaround for this issue is to make queries via GET, and use persisted queries that are pre-computed and stored on the server. Some GraphQL middleware services also offer caching.
At the moment, caching requests and responses are not supported in gRPC by default. However, it is possible to implement a custom middleware layer that will cache responses.
5.7. Intended Usage
REST, a good fit for domains, can be easily described as a set of resources as opposed to actions. Making use of HTTP methods enables standard CRUD operations on those resources. By relying on HTTP semantics, it’s intuitive to its callers, making it a good fit for public-facing interfaces. Good caching support for REST makes it suitable for APIs with stable usage patterns and geographically distributed users.
GraphQL is a good fit for public APIs where multiple clients require different data sets. Therefore, GraphQL clients can specify the exact data they want through a standardized query language. It is also a good choice for APIs that aggregate data from multiple sources and then serve it to multiple clients.
The gRPC framework is a good fit when developing internal APIs with frequent interaction between microservices. It is commonly used for collecting data from low-level agents like different IoT devices. However, its limited browser support makes it difficult to use in customer-facing web applications.
6. 混合搭配
每種API架構風格都有其優勢。然而,沒有一種方法適用於所有情況,我們選擇哪種方法將取決於我們的用例。
我們不必每次都做出單一選擇。我們也可以在解決方案架構中混合搭配不同的風格:
在上面的示例架構圖中,我們展示瞭如何將不同的API風格應用於不同的應用程序層。
7. 結論
在本文中,我們探討了用於設計 Web API 的三種流行架構風格:REST、GraphQL 和 gRPC。
我們考察了每種風格的應用場景,並描述了它們的優勢和權衡。
我們使用 Spring Boot 構建了一個簡單的服務,採用了這三種不同的方法。此外,我們通過考慮多個標準,對它們進行了比較。最後,由於沒有一種方法適用於所有情況,我們看到了如何在不同應用層混合和匹配不同的方法。