发布于 2026-01-05 1 阅读
0

API简史:RPC、REST、GraphQL、tRPC 背景 什么是API 冰河时代 铁器时代 现代 未来 👋 谁在使用tRPC? #1290

API简史:RPC、REST、GraphQL、tRPC

背景

什么是API?

冰河时代

铁器时代

现代

未来

👋 谁在使用 tRPC? #1290

背景

上周,我参加了由伦敦 Guild 主办的关于 GraphQL 与 tPRC 的讨论活动。tPRC 的创建者Alex和 urql GraphQL 核心团队成员Phil(urql 是一个高度可定制且功能强大的 JavaScript 及其框架的 GraphQL 客户端)就这两个阵营的常见用途和存在的问题进行了精彩的讨论。您可以在下方观看完整的讨论视频:

让我有点惊讶的是,有不少年轻人只用过 tRPC,因为它从零开始搭建框架确实非常高效,而我对 GraphQL 了解不多。所以,作为一个来自“冰河时代”的老开发者,我或许可以简单介绍一下 GraphQL 的发展历程。为什么呢?

正如尤瓦尔·诺亚·赫拉利在《人类简史》中所说

学习历史的最佳理由在于:不是为了预测未来,而是为了摆脱过去的束缚,去想象不同的命运。当然,这并非完全的自由——我们无法避免受到过去的影响。但总比没有自由要好。

希望这能让你看到更多选择。

什么是API?

我们先来看定义。维基百科的定义如下:

应用 程序编程接口API)是两个或多个计算机程序相互通信的一种方式。

在我们看来,这完全是关于客户端如何通过互联网与服务器收发消息。

API

冰河时代

如果你和我同龄,我敢打赌你见过或写过的第一个与网络相关的程序就是聊天应用。😄

在此期间,如果您想与服务器通信,需要使用操作系统提供的 Sockets 库(与 Socket.io 或 WebSocket 无关)来发送消息。服务器端的代码大致如下:

int main() {
    // Bind
    if (bind(server_socket, (struct sockaddr *) &server, sizeof(server)) < 0) {
        printf("Error binding\n");
        exit(1);
    }

    // Listen
    listen(server_socket, max_clients);

    // Accept an incoming connection
    printf("Listening for incoming connections...\n");
    int c = sizeof(struct sockaddr_in);
    while ((client_socket = accept(server_socket, (struct sockaddr *) &client, (socklen_t*) &c))) {
        printf("Connection from %s\n", inet_ntoa(client.sin_addr));

        // Send welcome message to client
        char *message = "Welcome to the chat room!\n";
        send(client_socket, message, strlen(message), 0);

        // Create new thread for incoming connection
        pthread_t sniffer_thread;
        if (pthread_create(&sniffer_thread, NULL, connection_handler, (void*) &client_socket) < 0) {
            printf("Error creating thread\n");
            exit(1);
        }
    }

    return 0;
}

void *connection_handler(void *socket_desc) {
    // Get the socket descriptor
    int sock = *(int*) socket_desc;
    int read_size;
    char buffer[BUFFER_SIZE], message[BUFFER_SIZE];

    // Receive a message from the client
    while ((read_size = recv(sock, buffer, BUFFER_SIZE, 0)) > 0) {
        printf("Client %d: %s", sock, buffer);
        snprintf(message, sizeof message, "Client %d: %s", sock, buffer);
    }
}
Enter fullscreen mode Exit fullscreen mode

所以,API 提供的仅仅是发送/接收字符位sendread函数。其余部分完全需要您自行完成。您可以想象,处理这些字符位进行业务往来是多么麻烦且容易出错。

铁器时代

由于问题主要出在处理网络传输的数据比特上,人们开始思考我们是否可以忽略网络环境,直接调用远程服务器上的函数,就像调用本地函数一样。这就催生了远程过程调用(RPC)。

实际上,活动结束后闲聊时,有个家伙说:“我还是不太明白 tRPC 的作用。” 我用 RPC 的定义回答他:“tRPC 的作用就是让你像用 TypeScript 调用本地函数一样调用远程函数。” 他好像就明白了。你看,这或许就是学习历史的一个理由吧。😄

为此,您首先需要在 IDL(接口定义语言)文件中定义您的 API,如下所示:

//file hello.idl
[
    uuid(7a98c250-6808-11cf-b73b-00aa00b677a7),
    version(1.0)
]
interface hello
{
    void HelloProc([in, string] unsigned char * pszString);
    void Shutdown(void);
}
Enter fullscreen mode Exit fullscreen mode

然后,您可以使用 IDL 编译器工具生成代码存根,以处理客户端和服务端消息的序列化和反序列化:

RPC

你熟悉吗?是的,这需要根据模式文件生成代码,GraphQL 现在也是如此。

虽然它简化了与服务器通信所需的工作,但也存在一些缺点:

  • 与底层系统紧密耦合。这使得客户端和服务器紧密耦合,因此更适合内部 API 而不是外部 API。
  • 可发现性差。无法通过分析 API 来了解其工作原理,也无法发送请求并根据请求内容来理解应该调用哪个函数。
  • 难以调试。数据序列化和反序列化规则由网络数据表示(NDR)定义,包括如何处理32位和64位系统中的指针数据,如果出现任何问题,调试起来都极其困难。

gRPC 与 tRPC

我看到很多人在问gPRC和tRPC的区别,因为它们看起来真的很像双胞胎。虽然它们都诞生于现代,但gRPC更像是RPC的直系后代,并具有一些更高级的特征,例如:

  • Protocol Buffer 作为 IDL(接口描述语言)
  • 使用 HTTP/2
  • 双向流和流量控制
  • 验证

而tRPC更像是它的远房亲戚,可能只是借用了它的姓氏。所以我们稍后再讨论它。

肥皂

在 REST 协议风靡全球之前,SOAP 协议早已存在。SOAP 由微软发布。乍一看,它似乎与 RPC 毫无关联,但如果你了解它的“父辈”——XML-RPC,或许就能明白其中的缘由。实际上,SOAP 继承了 RPC 的诸多特性。它使用 WSDL(Web 服务描述语言)作为其模式文件,而 WSDL 又以 XML 格式呈现,因此它具有语言和平台无关性,这使得不同的编程语言和 IDE 能够快速建立通信。此外,SOAP 在事务内部提供隐私性和完整性,同时允许在消息级别进行加密,从而满足企业级事务质量的要求。

我曾恰好在微软协议套件团队工作。当时SOAP被广泛采用,几乎所有新创建的协议都基于SOAP,只有一小部分是REST。

然而,REST 很快超越了它,并主导了世界。

为什么?

水既可以承载船只,也可以使船只倾覆。

  • XML 使其具有语言和平台无关性;但也正是 XML 导致它消耗更多带宽且处理速度较慢。

  • 正是这种安全性使其达到企业级品质;但也正是这种安全性,使得它在快速搭建项目框架时不太容易实现。

那么究竟是什么改变了这一切,使得这些缺点变得难以忍受呢?

正是移动互联网的蓬勃发展将我们带入了现代社会。

现代

休息

与其他架构不同,REST 没有严格的规则或标准工具包。它是一种架构风格,定义了一系列架构约束和约定。符合 REST 约束的 API 就是 RESTful API。

因此,它的主要优势在于简单直观。它使用标准的 HTTP 方法和以资源为中心的方式定义 API,如下所示:

休息

这就像把面向对象编程(OOP)的概念引入到API领域。REST之所以流行,是因为它能帮助所有人轻松定义RESTful API。

顺便说一句,我见过很多项目只使用 GET 和 POST,这完全不会影响 REST 所赋予的强大功能。

然而,正如我之前提到的,这种宽松的标准也造成了一些问题:

  • 随着项目规模的增长,代码变得难以维护且容易出错。
  • 这通常会导致前端和后端团队紧密耦合,因为 API 的任何更改都需要双方相互合作。
  • 需要多次调用 API 才能返回所需人员。
  • 它通常存在过度获取和获取不足的问题。
  • 有时,开发人员需要创建重复的 API 端点,以适应不同的客户端,例如浏览器和移动应用。

由于上述原因,GraphQL 应运而生,并改变了游戏规则。

GraphQL

解决标准松散问题的主要措施是恢复模式文件。

type Book {
  title: String
  author: Author
}

type Author {
  name: String
  books: [Book]
}

type Query {
  books: [Book]
  authors: [Author]
}

type Mutation {
  addBook(title: String, author: String): Book
}
Enter fullscreen mode Exit fullscreen mode

一旦定义了模式,它就成为唯一的数据源。后端团队和任何不同的前端团队都可以基于该模式独立工作。借助代码生成,编译器将验证请求,以确保服务器能够响应。

所有关于 REST 的问题都已得到解决,您可以直接从GraphQL 官方网站的首页上看到这一点

我最喜欢 GraphQL 的一个特点是客户端可以决定响应返回的内容,这意味着他们永远不需要向后端添加响应字段。

gql-gif

就目前而言,我认为对于拥有独立前后端团队的大型项目来说,GraphQL 是最高效的解决方案。只要有经验丰富的工程师设计出合适的模式,在规模扩展时就能获得丰厚的回报,尤其是在 API 需要对外调用的情况下。

未来

欢迎来到未来。如果你有创业想法,那就去实现它吧。

我的世界

别担心,现在容易多了。让我们看看这个世界能给我们带来什么。

tRPC

TRPC

虽然后缀名为 RPC,但它只是继承了在本地调用远程函数的概念,但方式更简单、更精简,无需 IDL 文件和代码生成过程。实际上,您创建本地程序的体验与创建 RPC 完全相同:

tRPC-gif

那么,紧密耦合问题和低可发现性问题又该如何解决呢?这些问题依然存在,但世界已经发生了变化。

随着 TypeScript 和 Next.js 等框架的发展,历史上首次出现了可以使用单一语言在单个项目中构建完整 Web 应用的情况。因此,它的耦合性非常强,而且由于可以直接在代码中找到定义,所以无需考虑可发现性。

虽然没有模式也存在一些缺点,例如客户端无法决定响应类型,或者项目规模扩大时难以组织函数。但请记住,tRPC 仅有两年历史;未来至关重要。tRPC 已在 Netflix、Pleo 和其他多家公司得到应用,其取得的成就本身就令人印象深刻。

trpc 星

如果您发现即使使用 tRPC,设计和实现仍然会占用您太多时间,那么您可以考虑了解一下ZenStack。它为 Prisma ORM 添加了一个灵活的访问控制策略(授权)层,并自动创建 API(REST 或 tRPC)以及前端钩子。

我们何不团结一致,共同创造更美好的未来?

亚历克斯

文章来源:https://dev.to/zenstack/a-brief-history-of-api-rpc-rest-graphql-trpc-fme