java宏定义_现代化的 Java (二十六)—— Akka Stream Graph

Java. 的 Stream API,有经验的 Java 工程师应该都不陌生了。它为数据处理逻辑提供了一组串化的操作序列接口,为流式的业务提供了一个整齐一致的接口。但是要指出的是,在常见的编程语言中,Java的标准库几乎是这方面最弱的。而Akka Stream 是一组非常强大的工具集,它允许我们用非常直观的方式定义数据流,不仅仅是线性流,还可以是有向图。例如官方给出的这个例子:

val 

它直接对应的这样一个逻辑:

0ded94de0b6a823bb4cc4a5b3617d705.png
图片来自 Akka Stream 官方文档

我相信很多同行即使没有scala的经验,对照这个图也能很容易的理解代码。但是这里面有一个问题,同样功能的代码在 Java 中是这样的:

final 

相比之下 Java 版本冗长的实在过分了。

这还是一个非常简单的图。做量化数据订阅的时候,我还写过一些更复杂的图,我需要把服务器订阅的数据解压,然后分离心跳数据和业务数据,响应后再从出口发送回去。这个过程用 Java 写实在是太不友善了。

但是好在我们有 Clojure ,这几天我想了一下,为 Akka Stream 做了一些 Clojure 的封装。

Scala 的 graph dsl 之所以简洁有力, `~>` 操作符功不可没。Java 因为没有操作符重载,只能用方法链代替,加上没有隐式类型,造成了大量形式上的冗余。

Clojure 的 s 表达式,可以用几乎任意字符来定义方法,加上宏,它可以把 Java 版的 graph dsl 封装到非常漂亮的程度。我们先看一下对应前面那个例子的 Clojure 版本代码:

(

这一段代码可以说干净漂亮很多,特别是定义边时,可以用 `|=>` 一次串起多个节点。

实际上,`|=>` 是一个“平凡”的 Clojure 函数:

(

它看起来比较笨拙,但是把这些“笨拙”的代码封装成简洁漂亮的形式,不正是高级语言的价值所在吗?

这个函数中,其实就是对给定的节点序列,逐个判断类型,调用对应的方法,把它们连城一个合法的 Graph DSL 线。这其中多少参考了 Akka Stream 的实现代码。

对于刚开始学习 Clojure 的同行,可以在这里看到 loop recur 实现递归逻辑的 clojue 风格代码。说起来没有智能的尾递归一直算是 Clojue 的一个短板,目前标准的尾递归定式就是用 recur 。

我这里没有用 scala 里的 `~>` 运算符,是因为 `~` 在 Clojure 里用于它的宏模板。如果我们在代码中加入这个字符,我不确认写在宏里是否会遇到麻烦,所以刻意回避了它,同理也没有使用 `#` 和 `@` 。想来想去,`|=>` 虽然麻烦一点,但是 S 表达式的形式特点使得它处理多个节点的串接时特别方便,多写一个字符也就可以接受了。

在这个代码中,展示的是 from 到 to 的连接逻辑,其中 via 操作的代码在另一个函数中:

(

对于静态语言,这种分派反而简单很多,不过把它封装起来后,我们就可以找回 Lisp 语族简洁的体验。我针对官方 [Working with Graphs] 提到的一些例子,构造了对应的 Clojure 实现,作为 akka-stream-clojure 库的测试代码,例如:

(

这个测试展示的是整数序列数据经过 filter 分离成奇偶两个数据流,在通过 zip 合成一个 pair 的逻辑,官方的 Scala 版本是:

val 

Java 版本我们就不放在这里了,实在是比较冗长。当然有一部分原因是 Clojure 自带了可以无限求值的惰性序列,这些Clojure工具库极大的提升了它的表达能力。

有 Akka 经验的朋友可能会知道,除了从 from 到 to 的 `~>` ,Akka Stream 还允许我们从 to 节点反向连接到 from ,这个运算符是 `<~` 。

对应的,我也实现了 `<=|` 操作符:

(

但是暂时我还没有为它写测试。

需要注意的一个细节就是,Graph DSL 的节点(Xxx)和它的图形节点(XxxShape)还是有区别的,一般来说我们需要把构造出来的逻辑节点 add 到构造器(builder)中,从 add 调用的返回值得到对应的图形节点。我提供了一些方法,封装了几个常用的节点构造逻辑,可以一步得到对应的图形节点,这个将来应该还会继续追加。这个约束也是我们一定要在 `|=>` 和其它工具方法中传入 builder 对象的原因。在 Scala 中,因为有强大(同时也是难以驾驭的)的 implicit 功能,我们可以进一步简化形式,把 builder “藏起来”。但是我想了很久,还是决定在 Clojure 里不做太复杂的“黑魔法”,朴素的把 builder 放在明处。考虑到 S 表达式和其它抽象带来的代码简化,多写一个 builder 还是可以接受的。虽然用宏可以模拟此类功能,但是需要做大量的工作,并且降低了代码的扩展能力,在工程上,还是要考虑代价和收益的平衡。

暂时这个库还只是针对 graph dsl,但是将来可能会尝试加入更丰富的功能,这样我们可以通过 clojure 代码,把 java 项目变得更简洁优雅,容易维护。但是接下来,可能我会先针对 akka-http 的 java api 做一些尝试。

相关代码在 MarchLiu/akka-stream-clojure

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值