Kotlin RPC 入门
代码示例: tutorial-kotlin-rpc-app
使用的插件:
Kotlin RPC (Remote Procedure Call,远程过程调用) 是 Kotlin 生态系统激动人心的新增成员,它构建于稳固的基础之上,并基于 kotlinx.rpc
库运行。
kotlinx.rpc
库使你能够仅使用常规的 Kotlin 语言构造,即可跨网络边界发起过程调用。因此,它提供了 REST 和 Google RPC (gRPC) 的替代方案。
在本文中,我们将介绍 Kotlin RPC 的核心概念并构建一个简单的应用程序。你随后可以在你自己的项目中求值该库。
前提
本教程假定你对 Kotlin 编程有基本理解。如果你是 Kotlin 新手,请考虑审阅一些入门材料。
为了获得最佳体验,我们建议使用 IntelliJ IDEA Ultimate 作为你的集成开发环境 (IDE),因为它提供了全面的支持和工具,将提高你的生产力。
什么是 RPC?
本地过程调用与远程过程调用
任何有编程经验的人都会熟悉过程调用的概念。这是任何编程语言中的基本概念。从技术上讲,这些是本地过程调用,因为它们始终发生在同一个程序中。
远程过程调用是指函数调用和参数通过某种方式在网络上传输,以便实现可以在单独的 VM/可执行文件内发生。返回值则沿相反路径传回发起调用的机器。
最简单的想法是,将发生调用的机器视为客户端,将实现驻留的机器视为服务器。然而,情况并非一定如此。RPC 调用可以双向发生,作为对等架构的一部分。但为了保持简单,我们假设采用客户端/服务器部署。
RPC 框架基础
任何 RPC 框架都必须提供某些基本要素。在传统 IT 基础设施中实现远程过程调用时,这些是必不可少的。术语可能不同,职责划分也可能不同,但每个 RPC 框架都必须提供:
- 声明将远程调用的过程的方式。在面向对象编程中,接口是逻辑选择。这可以是当前语言提供的接口构造,也可以是某种语言中立标准,例如 W3C 使用的 Web IDL。
- 指定用于参数和返回值的类型的方式。同样,你可以使用语言中立标准。然而,在当前语言中标注标准数据类型声明可能更简单。
- 辅助类,称为 ,它们将用于将过程调用转换为可在网络上传输的格式,并解包生成的返回值。这些存根可以在编译期或在运行时动态创建。
- 底层 ,它管理辅助类并监督远程过程调用的生命周期。在服务器端,此运行时需要嵌入到某种服务器中,以便它能够持续处理请求。
- 需要选择(或定义)协议来表示被调用的过程、序列化发送的数据以及在网络上传输信息。过去,一些技术从头定义了新协议(CORBA 中的 IIOP),而另一些则专注于重用(SOAP 中的 HTTP POST)。
编组与序列化
在 RPC 框架中,我们谈论 和 。这是打包和解包要在网络上传输的信息的过程。它可以被认为是序列化的超集。在编组中,我们序列化对象,但我们还需要打包有关被调用的过程和调用发生上下文的信息。
介绍了 RPC 的核心概念后,让我们通过构建一个示例应用程序来了解它们如何在 kotlinx.rpc
中应用。
你好,kotlinx.rpc
让我们创建一个通过网络订购披萨的应用程序。为了使代码尽可能简单,我们将使用基于控制台的客户端。
创建项目
首先,你将创建一个包含客户端和服务器实现的项目。
在更复杂的应用程序中,最佳实践是为客户端和服务器使用独立模块。然而,为了本教程的简单起见,我们将为两者使用一个单一模块。
- 启动 IntelliJ IDEA。
在欢迎屏幕上,点击 New Project。
或者,从主菜单中选择 File | New | Project。
- 在 Name 字段中,输入 KotlinRpcPizzaApp 作为你项目的名称。
- 保留其余默认设置,然后点击 Create。
通常,你会立即配置项目构建文件。然而,那是一个不会提高你对技术理解的实现细节,所以我们最后再回到那一步。
实现客户端
- 导航到 src/main/kotlin 并创建一个新的 Client.kt 文件。
- 打开 Client.kt 并添加以下实现: kotlin
你只需要 25 行代码即可为执行 RPC 调用做准备并实际执行。显然,这其中发生了很多事情,所以让我们将代码分解成几个部分。
kotlinx.rpc
库使用
kotlinx.rpc
易于集成到现有 KMP 应用程序中。 Ktor 客户端和 Kotlin RPC 都基于协程构建,因此你使用 runBlocking
来创建初始协程,并在其中执行客户端的其余部分:
TIP
请注意,runBlocking
专为快速原型和测试设计,而非生产代码。 接下来,你以标准方式创建 Ktor 客户端实例。kotlinx.rpc
在底层使用
installKrpc()
函数确保其被加载: 创建此 Ktor 客户端后,你将创建一个 KtorRpcClient
对象来调用远程过程。你需要配置服务器的位置以及用于传输信息的机制:
至此,标准设置已完成,你已准备好使用问题域特有的功能。你可以使用客户端创建一个实现 PizzaShop
接口方法的客户端代理对象:
然后你可以发起远程过程调用并使用结果:
请注意,在这一点上为你完成了大量工作。调用的细节和所有参数都必须转换为消息,通过网络发送,然后接收并解码返回值。这种透明的发生方式是初始设置的回报。
最后,我们需要像往常一样关闭客户端:
实现服务器
服务器端的实现分为两部分。首先,你需要创建我们接口的一个实现;其次,你需要将其托管在服务器中。
- 导航到 src/main/kotlin 并创建一个新的 Server.kt 文件。
- 打开 Server.kt 并添加以下实现: kotlin
显然,这不是一个真实世界的实现,但它足以让我们的演示运行起来。
实现的第二部分基于 Ktor 构建。
将以下代码添加到同一个文件中:
kotlin以下是详细说明:
首先,你创建 Ktor/Netty 实例,并使用指定的扩展函数进行配置:
kotlin然后,你声明一个扩展 Ktor Application 类型的设置函数。此函数安装
kotlinx.rpc
插件并声明一个或多个路由:kotlin在路由部分中,你使用
kotlinx.rpc
对 Ktor Routing DSL 的扩展来声明一个端点。与客户端一样,你指定 URL 并配置序列化。但在此情况下,我们的实现将监听该 URL 以处理传入请求:kotlin请注意,你使用
registerService
将接口的实现提供给 RPC 运行时。你可能希望有多个实例,但这是后续文章的主题。
添加依赖项
你现在已经拥有运行应用程序所需的所有代码,但目前它甚至无法编译,更不用说执行了。 你可以使用 Ktor 项目生成器配合 kotlinx.rpc 插件, 或者你可以手动配置构建文件。这也不是太复杂。
- 在 build.gradle.kts 文件中,添加以下插件: kotlin
Kotlin 插件的原因显而易见。至于其他插件,解释如下:
- 需要
kotlinx.serialization
插件来生成辅助类型,以便将 Kotlin 对象转换为 JSON。请记住,kotlinx.serialization
不使用反射。 - Ktor 插件用于构建将应用程序及其所有依赖项打包在一起的胖 JAR。
- 需要 RPC 插件来生成客户端的存根。
- 需要
- 添加以下依赖项: kotlin
这添加了 Ktor 客户端和服务器,
kotlinx.rpc
运行时的客户端和服务器部分,以及用于集成kotlinx.rpc
和kotlinx-serialization
的库。有了这些,你现在就可以运行项目并开始发起 RPC 调用了。
扩展示例
最后,让我们增加示例应用程序的复杂性,为未来的开发打下坚实基础。
- 在 PizzaShop.kt 文件中,通过包含客户端 ID 来扩展
orderPizza
方法,并添加一个viewOrders
方法,该方法返回指定客户端的所有待处理订单:kotlin你可以通过返回一个
Flow
而不是List
或Set
来利用协程库。这将允许你一次一片地将信息流式传输到客户端。 - 导航到 Server.kt 文件,并通过将当前订单存储在
map
oflist
中来实现此功能:kotlin请注意,每个客户端实例都会创建一个新的
PizzaShopImpl
实例。这通过隔离它们的状态来避免客户端之间的冲突。但是,它不解决单个服务器实例内的线程安全问题,特别是当多个服务并发访问同一实例时。 - 在 Client.kt 文件中,使用两个不同的客户端 ID 提交多个订单: kotlin
然后,你使用 Coroutines 库和
collect
方法迭代结果:kotlin - 运行服务器和客户端。当你运行客户端时,你将看到结果递增地显示:
创建了一个工作示例后,现在让我们深入探讨一切是如何运作的。特别是,让我们比较和对比 Kotlin RPC 与两个主要替代方案——REST 和 gRPC。
RPC 与 REST
RPC 的概念比 REST 早得多,至少可以追溯到 1981 年。与 REST 相比,基于 RPC 的方法不限制你使用统一接口(例如 HTTP 请求类型),在代码中更易于使用,并且由于二进制消息传递而具有更高的性能。
然而,REST 有三个主要优势:
- 它可以直接由浏览器中的 JavaScript 客户端使用,因此可以作为单页应用程序的一部分。由于 RPC 框架依赖于生成的存根和二进制消息传递,它们与 JavaScript 生态系统不太兼容。
- 当特性涉及网络时,REST 会使其显而易见。这有助于避免 Martin Fowler 指出的分布式对象反模式。当团队将其面向对象设计拆分为两个或多个部分,而未考虑将本地过程调用变为远程调用所带来的性能和可靠性影响时,就会发生这种情况。
- REST API 基于一系列约定构建,这些约定使其相对容易创建、文档化、监控、调试和测试。有一个庞大的工具生态系统来支持这一点。
这些权衡意味着 Kotlin RPC 最适合在两种场景中使用。首先,是在使用 Compose Multiplatform 的 KMP 客户端中;其次,是在云中协作的微服务之间。Kotlin/Wasm 的未来发展可能会使 kotlinx.rpc
更适用于基于浏览器的应用程序。
Kotlin RPC 与 Google RPC
Google RPC 是目前软件行业中主导的 RPC 技术。一种名为 Protocol Buffers (protobuf) 的标准用于使用语言中立的接口定义语言 (IDL) 定义数据结构和消息负载。这些 IDL 定义可以转换为各种编程语言,并使用紧凑高效的二进制格式进行序列化。Quarkus 和 Micronaut 等微服务框架已经支持 gRPC。
Kotlin RPC 很难与 gRPC 竞争,这对 Kotlin 社区也没有益处。幸好,目前没有这方面的计划。相反,其意图是让 kotlinx.rpc
与 gRPC 兼容且可互操作。kotlinx.rpc
服务将能够使用 gRPC 作为其网络协议,而 kotlinx.rpc
客户端将能够调用 gRPC 服务。kotlinx.rpc
将使用其自己的 kRPC 协议作为默认选项(如我们当前示例所示),但没有什么能阻止你选择 gRPC。
后续步骤
Kotlin RPC 将 Kotlin 生态系统扩展到新方向,为创建和使用服务提供了 REST 和 GraphQL 的替代方案。它基于 Ktor、协程和 kotlinx-serialization
等成熟的库和框架构建。对于希望利用 Kotlin Multiplatform 和 Compose Multiplatform 的团队,它将为分布式消息传递提供一个简单高效的选项。
如果本介绍激发了你的兴趣,请务必查看 官方的 kotlinx.rpc
文档和示例。
kotlinx.rpc
库尚处于早期阶段,因此我们鼓励你探索它并分享你的反馈。错误和特性请求可以在 YouTrack 上找到,而一般性讨论则在 Slack 上进行(请求访问)。