1. 概述

多年来,REST 一直是 Web API 设计的事实标准。但 GraphQL 和 gRPC 的出现解决了 REST 的一些局限性。每种 API 方案都有显著优势,也存在相应权衡

本文将先分析三种 API 设计方案,然后用 Spring Boot 分别实现一个简单服务。接着通过多个关键维度对比它们,最后说明如何在不同应用层混合使用这些方案。

2. REST

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 注解定义控制器类。通过 @GetMapping 等注解绑定 HTTP 方法和资源路径:

@GetMapping("/rest/books")
public List<Book> books() {
    return booksService.getBooks();
}

使用 MockMvc 进行集成测试,它封装了所有 Web 应用组件:

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. 优缺点

最大优势:技术圈最成熟的 API 架构风格,开发者熟悉度高
灵活性陷阱:不同开发者对 REST 的理解可能存在差异

监控友好:每个资源有独立 URL,便于限流和监控
缓存简单:直接利用 HTTP 缓存机制,减少客户端-服务器交互

数据获取问题

  • 欠获取(Under-fetching):获取嵌套实体需多次请求
  • 过获取(Over-fetching):无法按需获取字段,总是返回完整数据

3. GraphQL

GraphQL 是 Facebook 开发的开源 API 查询语言。

3.1. 架构风格

GraphQL 提供查询语言和执行框架。不依赖 HTTP 方法操作数据(主要用 POST),而是通过:

  • 查询(Query):请求数据
  • 变更(Mutation):修改数据
  • 订阅(Subscription):实时数据更新

客户端驱动:客户端可精确声明所需数据,单次请求获取所有信息。

3.2. 示例服务

GraphQL 中数据通过 Schema 定义(对象、字段、类型):

type Author {
    firstName: String!
    lastName: String!
}

type Book {
    title: String!
    year: Int!
    author: Author!
}

type Query {
    books: [Book]
}

Spring 实现类似 REST,用 @RestController@QueryMapping 注解:

@QueryMapping
public List<Book> books() {
    return booksService.getBooks();
}

使用 HttpGraphQlTester 进行集成测试:

this.graphQlTester.document(document)
  .execute()
  .path("books")
  .matchesJson(expectedJson);

测试需在 POST 请求体中指定查询:

$ curl -X POST -H "Content-Type: application/json" -d "{\"query\":\"query{books{title}}\"}" http://localhost:8082/graphql

3.3. 优缺点

按需获取:仅返回客户端请求的数据,减少网络传输
规范严格:相比 REST 的模糊性,提供更明确的规范
调试友好:详细错误描述 + 自动生成变更文档

缓存困难:查询多样性导致中间代理缓存失效
⚠️ 性能风险:复杂查询可能压垮服务器,需限制查询复杂度

4. gRPC

gRPC 是 Google 开发的高性能开源 RPC 框架。

4.1. 架构风格

基于客户端-服务器模型的远程过程调用(RPC)。客户端像调用本地方法一样直接调用服务器方法强契约模式:客户端和服务器需共享相同 Schema 定义。

使用 Protocol Buffers(protobuf)定义请求/响应类型,编译器生成服务器和客户端代码。支持多种交互模式:

  • 传统请求-响应
  • 服务端流(单请求多响应)
  • 客户端流(多请求单响应)

通过 HTTP/2 传输紧凑的二进制格式,编解码效率极高。

4.2. 示例服务

首先定义 Schema(服务、请求/响应结构):

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 生成代码:

<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 类:

@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();
}

集成测试需特殊注解(@SpringBootTest + @SpringJUnitConfig + @DirtiesContext):

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);

使用 grpcurl 工具测试:

$ grpcurl --plaintext localhost:9090 com.baeldung.chooseapi.BooksService/books

4.3. 优缺点

性能卓越:二进制格式 + 高效编解码 + HTTP/2
代码生成:支持多语言,减少样板代码
安全默认:强制 HTTP/2 + TLS/SSL
跨语言支持:语言无关的契约定义

生态局限:开发者社区接受度低于 REST
调试困难:二进制格式不可读,需专用工具
⚠️ 浏览器限制:现代浏览器仅通过 TLS 支持 HTTP/2

5. 如何选择API方案

5.1. 数据格式

  • REST:最灵活,支持 JSON/XML 等多种格式
  • GraphQL:使用专用查询语言,响应为 JSON(转换其他格式影响性能)
  • gRPC:强制使用 Protocol Buffers(二进制格式,不可定制)

5.2. 数据获取

  • GraphQL最高效,客户端精确控制返回字段
  • REST/gRPC:需开发新接口或过滤器才能避免过获取/欠获取

5.3. 浏览器支持

  • REST/GraphQL所有现代浏览器原生支持
  • gRPC:需 gRPC-Web 扩展(基于 HTTP 1.1,功能受限)

5.4. 代码生成

  • GraphQL:需额外库,代码生成可选(可用任意匹配 Schema 的 POJO)
  • gRPC:强制代码生成步骤,需映射自定义 POJO 到生成类型
  • REST:无强制要求,可通过 OpenAPI 生成代码

5.5. 响应时间

  • gRPC显著更快(二进制格式 + HTTP/2),性能测试显示比 REST 快 5-10 倍
  • REST/GraphQL:基于 HTTP 1.1,延迟较高

5.6. 缓存

  • REST简单成熟,直接利用 HTTP 缓存(浏览器/代理/CDN)
  • GraphQL:默认困难(POST + 动态查询),需持久化查询等变通方案
  • gRPC:无原生支持,需自定义中间件

5.7. 适用场景

  • REST:适合资源导向的领域(如 CRUD 操作),公共接口首选,缓存友好
  • GraphQL:适合多客户端需不同数据集的公共 API,或聚合多数据源的场景
  • gRPC:适合微服务间高频通信,或 IoT 设备等低层数据采集,不推荐面向客户的 Web 应用

6. 混合使用方案

没有万能方案,可在架构中混合使用不同风格

REST、GraphQL 和 gRPC 混合架构示例

上图展示了不同应用层使用不同 API 风格的典型架构。

7. 总结

本文深入分析了三种主流 Web API 架构风格:REST、GraphQL 和 gRPC。我们通过 Spring Boot 实现了相同服务的三种版本,从数据格式、性能、缓存等维度对比了它们的优劣。

关键结论

  • REST 仍是公共 API 的安全选择
  • GraphQL 解决了数据获取痛点,适合复杂前端场景
  • gRPC 在微服务通信中性能无敌

最终选择应基于具体需求:没有银弹,只有最适合当前场景的方案。必要时可混合使用,发挥各自优势。

完整源码见 GitHub 仓库


原始标题:REST vs. GraphQL vs. gRPC – Which API to Choose?