API简史:RPC、REST、GraphQL、tRPC
背景
什么是API?
冰河时代
铁器时代
现代
未来
👋 谁在使用 tRPC? #1290
背景
上周,我参加了由伦敦 Guild 主办的关于 GraphQL 与 tPRC 的讨论活动。tPRC 的创建者Alex和 urql GraphQL 核心团队成员Phil(urql 是一个高度可定制且功能强大的 JavaScript 及其框架的 GraphQL 客户端)就这两个阵营的常见用途和存在的问题进行了精彩的讨论。您可以在下方观看完整的讨论视频:
让我有点惊讶的是,有不少年轻人只用过 tRPC,因为它从零开始搭建框架确实非常高效,而我对 GraphQL 了解不多。所以,作为一个来自“冰河时代”的老开发者,我或许可以简单介绍一下 GraphQL 的发展历程。为什么呢?
正如尤瓦尔·诺亚·赫拉利在《人类简史》中所说:
学习历史的最佳理由在于:不是为了预测未来,而是为了摆脱过去的束缚,去想象不同的命运。当然,这并非完全的自由——我们无法避免受到过去的影响。但总比没有自由要好。
希望这能让你看到更多选择。
什么是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);
}
}
所以,API 提供的仅仅是发送/接收字符位send的read函数。其余部分完全需要您自行完成。您可以想象,处理这些字符位进行业务往来是多么麻烦且容易出错。
铁器时代
由于问题主要出在处理网络传输的数据比特上,人们开始思考我们是否可以忽略网络环境,直接调用远程服务器上的函数,就像调用本地函数一样。这就催生了远程过程调用(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);
}
然后,您可以使用 IDL 编译器工具生成代码存根,以处理客户端和服务端消息的序列化和反序列化:
你熟悉吗?是的,这需要根据模式文件生成代码,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
}
一旦定义了模式,它就成为唯一的数据源。后端团队和任何不同的前端团队都可以基于该模式独立工作。借助代码生成,编译器将验证请求,以确保服务器能够响应。
所有关于 REST 的问题都已得到解决,您可以直接从GraphQL 官方网站的首页上看到这一点。
我最喜欢 GraphQL 的一个特点是客户端可以决定响应返回的内容,这意味着他们永远不需要向后端添加响应字段。
就目前而言,我认为对于拥有独立前后端团队的大型项目来说,GraphQL 是最高效的解决方案。只要有经验丰富的工程师设计出合适的模式,在规模扩展时就能获得丰厚的回报,尤其是在 API 需要对外调用的情况下。
未来
欢迎来到未来。如果你有创业想法,那就去实现它吧。
别担心,现在容易多了。让我们看看这个世界能给我们带来什么。
tRPC
虽然后缀名为 RPC,但它只是继承了在本地调用远程函数的概念,但方式更简单、更精简,无需 IDL 文件和代码生成过程。实际上,您创建本地程序的体验与创建 RPC 完全相同:
那么,紧密耦合问题和低可发现性问题又该如何解决呢?这些问题依然存在,但世界已经发生了变化。
随着 TypeScript 和 Next.js 等框架的发展,历史上首次出现了可以使用单一语言在单个项目中构建完整 Web 应用的情况。因此,它的耦合性非常强,而且由于可以直接在代码中找到定义,所以无需考虑可发现性。
虽然没有模式也存在一些缺点,例如客户端无法决定响应类型,或者项目规模扩大时难以组织函数。但请记住,tRPC 仅有两年历史;未来至关重要。tRPC 已在 Netflix、Pleo 和其他多家公司得到应用,其取得的成就本身就令人印象深刻。
如果您发现即使使用 tRPC,设计和实现仍然会占用您太多时间,那么您可以考虑了解一下ZenStack。它为 Prisma ORM 添加了一个灵活的访问控制策略(授权)层,并自动创建 API(REST 或 tRPC)以及前端钩子。
我们何不团结一致,共同创造更美好的未来?
文章来源:https://dev.to/zenstack/a-brief-history-of-api-rpc-rest-graphql-trpc-fme








