上周花了5天时间用 Kotlin/Native 重写了一个 Python 程序,简单记录一下想法。
Kotlin 2.0
Kotlin 近期发布了 2.0,新的 K2 编译器也正式发布了!我在本地(XPS 17)上做了简单的测试,编译一个 Kotlin/Native 的 Hello World 程序:
package me.zzp
fun main() {
println("Hello world!")
}
编译命令是:
./gradlew --parallel linkDebugExecutableNative
./gradlew --parallel linkReleaseExecutableNative
对比结果如下:
| 项 | Debug | Release |
|---|---|---|
| 首次编译时间 | 1s | 9s |
| 增量编译时间 | 0.9s | 6s |
| 文件尺寸(strip前) | 5.7MB | 452KB |
| 文件尺寸(strip后) | 964KB | 320KB |
这个文件尺寸实在太吸引我了!而且编译时间也能忍受。正好最近有个 Python 程序需要升级,于是决定用 Kotlin/Native 重写。
Python项目重写
待重写的 Python 程序是一个数据同步工具,可以看作是一款轻量级的 ETL:负责在多张多维表格、三方系统之间相互同步数据,以及一些简单的数据处理。数据的获取与提交方式都是调用相应系统的 HTTP/S 接口。
程序一开始逻辑很简单:每新增一家需要对接接口的下游机构,就新建一张与之对应的多维表格;然后定时扫描多维表格,当新增了数据后,就调用机构接口提交数据;同时调用机构数据状态查询接口,把数据最新状态同步回多维表格中。
最近上游机构也提出要与我们系统对接;并且内部还需要对部分上游推送的数据做审核、筛选,然后分配给指定的下游;接着再从下游同步最新状态回来,同时推送给上游。即数据流形成了从“上游系统”到"数据池"、再到“下游系统”、再回到"数据池",最后回到“上游系统”这样的回路。
数据流转过程大同小异,无非是从数据源 A 中查询出符合条件的记录,然后保存到数据源 B 中。我转念一想,如果数据源都变成关系数据库的表,就能很方便地用 SQL 在表之间同步数据了:
select
字段1,
字段2,
...
from
数据源A
where
...
into
数据源B
Kotlin DSL
比较简便的方式是把这些 HTTP 接口映射成 PostgreSQL 或 H2 Database 的 Foreign Table,然后用 SQL 去查询、处理和保存数据。但为了折腾 Kotlin/Native,我打算自定义一套类 SQL 的 DSL,提供 select、insert、update、delete 四个 CRUD 操作。效果如以下代码所示:
select(
上游编号,
跟进状态,
顾问备注
).from(
张泽鹏的客户
).where(
跟进状态 ne "未添加"
).map {
Record(
id = it[上游编号],
跟进状态 to it[跟进状态],
顾问备注 to it[顾问备注],
是否有效 to (it[跟进状态] != "无效客户"
&& it[跟进状态] != "同行"),
更新时间 to now,
)
}.updateTo(线索池)
上述代码从“张泽鹏的客户”这张多维表格中同步跟进状态到“线索池”这张表,并且是合法的 Kotlin 代码,这可比之前用 for 循环、getter、setter 美观多了。实现这套 DSL 的过程遇到不少坎坷,我采用了以下这些功能或类库:
- Ktor CIO Server:HTTP 服务端。开放 RESTful 接口供上游渠道方调用。
- Ktor Curl Client:HTTP 客户端。调用飞书多维表格和三方系统的接口。
- Kotlinx Serialization:序列化和反序列化 JSON 数据。
- Kotlinx Coroutines:用协程实现每隔 1 分钟轮询功能。
cinterop调用 OpenSSL:用于数据 AES ECB 加密。platform.posix.*:读写本地文件。用于缓存飞书开放平台等 Access Token。
整体体验都还不错,虽然文档比较欠缺,但借助 Kimi 等 AI 工具,也能快速得到需要的答案。唯一让我沮丧的是:当加入 cinterop 调用 OpenSSL 后,编译时间显著增加,需要花费 30 多秒,导致无法愉快地边修改边运行测试,比较影响开发效率。
我的感受
我关注 Kotlin/Native,是为了找一门语法更加现代的编程语言代替 C/C++ 做日常开发。就目前的体验,我认为 Kotlin/Native 之于 C/C++,犹如当年 CoffeeScript 之于 JavaScript,前者之于后者都只限于编程语言层面的改进,属于自己的类库生态都还不成熟。
对于那些需要依赖 C/C++ 生态库,但又希望用更优秀的编程语言来开发的,我觉得 Kotlin/Native 是一个有吸引力的选择,用 cinterop 调用 C/C++ 库非常简单;但如果没有 C/C++ 的包袱,是从零开始的新项目,我会更倾向于用 Rust。