自去年接手 ArchGuard 到现在,差不多是一年,也差不多是我真正(虽然也是业余时间)写 Kotlin 一年的时间。在不考虑 CLI 体制的体积下,Kotlin 确实是一个快乐的语言,特别是:你经常找不到北,但是非常好用的语法糖:extension functions。在那之前,我业余经常使用的是 Rust,有些语法糖虽然好用,但是吧,有时候编译器就不让你快乐。我觉得主要原因吧,应该是:Kotlin 是 Jetbrains 的亲儿子,而 Rust 不是。次要原因是:Rust 的静态分析真不是一件容易的事。
回到文章的正题,也是因为 ArchGuard Scanner 在语法分析完之后,需要进行一系列的数据处理和就地分析、交互式分析,也因此需要一系列的数据科学工具。
当然,我们并不会详细展开每一方面。
在我们设计《ArchGuard 的架构工作台》时,旨在提供一个交互式的架构分析环境,以帮助人们设计架构、演进架构、观测架构。从结论上来说,我们要提供一个架构领域的 Jupyter,架构师就能像数据科学家一样进行分析。而软件架构师与数据科学家不同的是,严谨性的分析需要的是静态类型语言。
于是,去年我们引入了还在开发中的、不稳定的 Kotlin Jupyter 成为了我们的选择。Kotlin Jupyter 是一个交互式的 Kotlin 编程环境,它结合了 Kotlin 语言和 Jupyter Notebook 的优点,并提供了一个方便的方式来进行 Kotlin 语言的交互式编程和数据分析。对于开发一个交互式分析平台来说,我们做的工作如下:
jupyter-api
、jupyter-kernel
作为运行 Kotlin Script 的基础环境%use
构建了自己的架构即代码库,以提供底层的 API 封装最后,ArchGuard 的工作台 REPL 示例代码如下:
%use archguard
repos {
repo(name = "Backend", language = "Kotlin", scmUrl = "https://github.com/archguard/archguard")
}
context.repos.create()
作为一个还在频繁变动的库,对于三方系统的接入,还是存在一些问题的:
0.11.0-89-1
版本,因为先前的接口无法直接使用,所以用的是 Jetbrains 的内部源,直到今年更新到 0.11.0-208
才算稳定。不过,现在勉强算是可以跑起来,也是可喜可贺。现在想想,当时应该去提几个 issue 来着的。
Kotlin DataFrame 是我们最近正在计算引入就地数据分析时考虑的工具,它提供了与 Pandas 类似的数据结构,即 DataFrame,可以对数据进行索引、切片、过滤、分组和聚合等操作。与 Pandas 稍有不同的是,Kotlin DataFrame 采用了类型安全的设计,并在设计时考虑了 Kotlin 的语言特性,如空安全和函数式编程等。
Kotlin DataFrame 提供了各种数据读写工具,可以从 ArrowFeather、CSV、Excel、JSON、TSV 等格式中读取数据,并将数据保存到这些格式或数据库中。
val dataFrame = DataFrame.read("data/one_data.json").cast<CodeDataStruct>()
// or
val ds = listOf(
CodeDataStruct(
NodeName = "Main",
Functions = listOf(CodeFunction(Name = "main")),
)
).toDataFrame()
随后,我们就可以一些统计分析和机器学习相关的功能:
dataframe
.filter { it[CodeDataStruct::Functions].isNotEmpty() }
.filter { it[CodeDataStruct::Functions].any { function -> function.Name == "main" } }
.print()
ArchGuard Scanner 在数据分析后,就会输出分析完的数据。那么下一个分析任务,就可以直接进行处理,诸如于 SQL Lint、API Lint 等。此时,我们就可以解耦几种不同类型的任务,唯一的问题就是过程中序列化产生的开销,这也就是下一小节要解决的问题。
同样的,我们所经历了和 Kotlin Jupyter 相似的痛苦 —— 文档缺失,最后还是去看源码里的测试用例了。
ArchGuard 1.x 里,不存在数据传输的场景,Scanner 直接生成 SQL 然后导入数据库。在 ArchGuard 2.0 里,我们使用 JSON 来传输代码的元数据,在数据量大场景下,服务端的 OutOfMemory 成为了日常的 issue —— 虽然系统在设计之初不是给单体系统设计的,微服务架构下这么大的代码量本身就不合理。在这时,有更好的方案是引入流式系统机制,其对于现有系统的改动太大,需要有专人来负责;另外一种比较简单的方案,就是服务器不需要一个反序列化的数据格式,就成了一种更简单的选择。
Apache Arrow 与 FlatBuffers 是我们考虑的选择,而 DataFrame 支持了 Aapche Arrow 格式的转换,这就让处理工作变得相当简单:
val dataFrame = DataFrame.read("data/onedata.arrow").cast<CodeDataStruct>()
dataFrame
.print(10)
不过,在我们引入 ArchGuard 的过程中,发现它并不支持类型嵌套 —— 原因是开发人员在设计的时候漏了:issues 271,我尝试去修复这个 issue,但是发现我需要一系列的先验知识,就暂时性放弃了。希望官方有空早日修复这个 issue,这样我就可以进行后续的发电工作。接下来,在有了数据之后,我们就可以用 Lets-Plot
来绘图:
var p = letsPlot(data)
p += geomDensity(color="dark_green", alpha=.3) {x="rating"; fill="cond"}
p + ggsize(700, 350)
在 Jetbrains IDE 支持下,结合 Kotlin 的特性,它变得很强大。诸如于,我们可以使用 DataSpell + Jupyter 来实现实时数据分析。
其它的一些点诸如于:
当然,除了 Kotlin 相关的库,Java 生态中也有一系列库可以满足你的需求。不过 Kotlin 的语法更加舒服,如果你想了解更多关于 Kotlin 在数据科学中的应用,可以查阅 Kotlin 官方文档:https://kotlinlang.org/docs/data-science-overview.html。
其它:除了上述提到的库,Kotlin 社区还有一个名为 Koma 的科学计算框架,可以在 Kotlin 中进行数学运算和矩阵计算。不过,由于该库目前较为不活跃,我们未展开详细研究。
在现有的生态体系里,主流的数据分析语言包含(主要是我就熟悉这几个):
在诸多方面,Kotlin 与 Scala 相似,在上手的学习曲线上,Kotlin 更为简单。虽然,我也在公司项目里用过 Scala,但是要重写现有的 ArchGuard 是不可能的,除非 Copilot 哪天能帮我自动重写。所以,探索使用 Kotlin 进行数据科学更为了一种更好的选择。除此,Kotlin 也更适合于那些希望快速学习和使用 Java 风格编程语言的数据科学开发者。
围观我的Github Idea墙, 也许,你会遇到心仪的项目