akka typed mysql_现代化的 Java (三十四)—— Akka Typed 的 Clojure 封装和简化

这几天在家读了一下 Akka Typed 的文档,发现还是挺有意思的。

这个体系已经是 Akka 官方推荐的默认风格,现在打开 Akka 官网的最新版 Akka 文档(2.6.0),入门教程就是 typed 。以前的 Actor 风格被称为 classic ,仍然支持。

Typed 引入了新的 Actor 类型,称为 Behavior。相对于 classic Actor, 有几个明显的变化:

* 简化了 receiver 的封装,Java API 不再要求 Receiver 是一个 PartialFunction ,只要能返回一个 Behavior 类型即可(当然,仍然可以用 Match Class 风格的消息分派封装方法)。

* 鼓励显式的将状态和行为分离,将状态只读,状态变化就构造一个新的状态,然后用新状态构造一个新的 Behavior 对象。Behavior 本身是泛型化的,对于 Java/Scala ,更有利于构造严谨的项目。

相对于 Classic Actor,Behavior 的构造风格更简明直接。

这些变化,很多都不是硬门槛,而是以前就存在于 Akka 体系中的,但是现在 typed 的设计,促使它将这些风格(大多是明显的函数式编程倾向)突出出来。

而这些风格变化,除了强化类型约束,其它对 Clojure 都是有利的。甚至可以说,Typed Behavior 的状态管理,天然就符合 Clojure 的编程风格。

另一方面,扔掉 typed 部分,至少对于 Clojure 这部分没有什么损失,因为 JVM 的擦拭法泛型,实际上在运行时,所有的类型信息都扔掉了。

我尝试对这部分代码做了简单的封装:

(defnbehavior

([state receiver]

(StateActor/create state receiver))

([receiver]

(Actor/create receiver)))

这个函数根据是否需要携带状态,分别调用了两个 Java 封装,它们负责将 Clojure 函数封装为 Behavior 对象。这部分实现稍后我们进行讨论。

构造 Typed System 的时候,我们要给它设定默认的 Behavior ,如下面这个测试:

(deftest basic-test

"tests for basic actor workflow"

(let[test-kit (ActorTestKit/create)

probe (.createTestProbe test-kit)

actor (spawn test-kit (behavior

(fnreceiver [^ActorContext context ^Map message]

(case (:category message)

:inc

(! (:sender message) (->message :number inc))

:dec

(! (:sender message) (->message :number dec))

(! (:sender message) (->message :number)))

same)))

self (.ref probe)]

(! actor {:category :inc :number 100 :sender self})

(.expectMessage probe 101)

(! actor {:category :dec :number 100 :sender self})

(.expectMessage probe 99)

(! actor {:category :save :number 100 :sender self})

(.expectMessage probe 100)

(.shutdownTestKit test-kit)))

Akka Context 提供 spawn 方法,将给定的 behavior 封装为 ActorRef ,System 和 actor 都可以使用同样的 spawn 操作。经过封装,这个过程已经非常接近 Scala 的版本。

(defnspawn

([context ^Behavior behavior]

(.spawn context behavior))

([context ^Behavior behavior ^String name]

(.spawn context behavior name)))

当然,我们也可以进一步合并 System和Behavior 的定义,形如:

(defnsystem

([receiver name]

(ActorSystem/create (behavior receiver) name))

([state receiver name]

(ActorSystem/create (behavior state receiver) name)))

同样,我们可以用这样的函数简化 spawn 操作:

(defnspa

[context receiver & options]

(let[{:keys [namestate]} options

behavior (ifstate

(behavior state receiver)

(behavior receiver))]

(ifname

(spawn context behavior name)

(spawn context behavior))))

这个 spa 不是指的 spa,而是 spawn 的简化。形如下面的测试逻辑,我们可以将这个操作简化到:

(deftest spa-test

"tests for basic actor workflow using spa"

(let[test-kit (ActorTestKit/create)

probe (.createTestProbe test-kit)

actor (spa test-kit

#(let[sender (:sender %2)

n (:number %2)]

(case (:category %2)

:inc (! sender (incn))

:dec (! sender (decn))

(! sender n))

same))

self (.ref probe)]

(! actor {:category :inc :number 100 :sender self})

(.expectMessage probe 101)

(! actor {:category :dec :number 100 :sender self})

(.expectMessage probe 99)

(! actor {:category :save :number 100 :sender self})

(.expectMessage probe 100)

(.shutdownTestKit test-kit)))

这里有一个细节,就是我们通过关键字参数 :name 和 :state 分别传入 behavior 的名字和状态。根据参数组合, spa 函数执行不同的构造逻辑。而在 Scala 中,我们可以通过 implicits 实现更精细的自动转换。

我在封装 typed 的时候,主要考虑在损失类型检查之后,尽可能的发挥 Clojure 的便利,如果需要更严谨的工程控制,直接使用 Scala 版本应该是更好的选择。

因此,无状态的 typed actor ,被我封装为:

public class Actor extends AbstractBehavior {

private IFn receiver;

private Actor(ActorContext context, IFn receiver) {

super(context);

this.receiver = receiver;

}

public static Behavior create(IFn receiver) {

return Behaviors.setup(context -> new Actor(context, receiver));

}

private Behavior onMessage(Map message) {

return (Behavior)receiver.invoke(getContext(), message);

}

@Override

public Receive createReceive() {

return newReceiveBuilder().onMessage(Map.class, this::onMessage).build();

}

}

有状态的 typed actor ,也没有很复杂:

public class StateActor extends AbstractBehavior {

private Map state;

private IFn receiver;

private StateActor(ActorContext context, Map state, IFn receiver) {

super(context);

this.state = state;

this.receiver = receiver;

}

public static Behavior create(Map state, IFn receiver) {

return Behaviors.setup(context -> new StateActor(context, state, receiver));

}

private Behavior onMessage(Object message) {

return (Behavior)receiver.invoke(getContext(), state, message);

}

@Override

public Receive createReceive() {

return newReceiveBuilder().onAnyMessage(this::onMessage).build();

}

}

对于 akka typed actor 的 receiver ,我们要求它的返回值必须是 behavior,要么是携带业务逻辑的 behavior ,要么是 same(表示没有状态变化)或 stopped 这类“系统消息”。

我也封装了一些 typed 的功能性 api,例如 Behaviors 类型的 receive 和setup 方法。但是这里并没有追求一步到位,完整的覆盖所有功能,我接下来准备尝试编写一些基于 akka typed 的例子,在使用中探索下一步的开发方向。

本文中展示的代码,我发布为 akka-typed-clojure 项目,地址在:https://github.com/MarchLiu/akka-typed-clojure 。这个项目还处于很初级的阶段,估计要写几个 Akka 应用之后,才会逐步稳定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值