Akka Typed は既存の Classic Actor の課題を解決しコードをよりシンプルかつ堅牢なものにしてくれるなど、導入することで得られるメリットは決して小さくないのですが、書籍「Akka 実践バイブル (Akka in Action)」でもほんの少ししか触れられていないなどまだ日本語の情報が十分ではない状況です。
簡単な内容ではありますが、本記事が Akka Typed 導入の参考になれば幸いです。
Akka Typed について
Akka Typed は Akka 2.6.0 から導入された新しい Actor の API です。それまでの Actor API は Classic Actor と呼ばれ区別されています。Akka の公式ドキュメントではすでに Akka Typed での説明がメインになっています。
Actor の記述方法が Classic Actor とはだいぶ違っているので最初は戸惑いますが、やっていることは Message を receive して何らかの処理をするだけなのでその点においては Classic Actor と変わりません。
Classic Actor で少し残念な部分の一つはメッセージの型を宣言できないことではないかと思います。パターンマッチでフィルタリングすればいいのですが、実行時に動的に型判定することになります。せっかく Scala で書いているのだから Actor も Type Safe に静的に書きたくなるものです。Typed Actor はその名の通りまさにその課題を解決してくれます。
Actor 側でも、受け取る型が決まっているので case _ => のようなフォールバックコードが不要になります。また、網羅性のチェックも Scala のパターンマッチの仕組みを利用してコンパイル時に行うことができます。そのように全体的にコードがすっきりして見通しがよくなりかつ安全性も高まります。
Immutable なコードで Actor を実装できる
Actor モデルはその概念上ステートフルであるため、Actor は基本的に Mutable なインスタンスになります。そのため Actor に何かしら状態を管理させたい場合は Mutable なプロパティを持たせる必要があります。これはそういうものなので、まあ、そういうものなのですという感じなのですが、Akka Typed では関数型的な仕組みを提供していて、関数の再帰処理で Actor を記述できるようになっています。
先程のサンプルコードを再掲します。
(1) MyTypedActor は numValue という Int のプロパティを持つ Actor です。しかしコード内には numValue を保持するためのインスタンス変数が存在していません。
(2) apply() 関数の中で呼び出されている Behaviors.receive が Actor を生成する関数です。receive の引数には Actor の処理を関数として渡しています。関数が受け取る引数は ActorContext と Message になります。ActorContext は ActorSystem を参照したりロガーを取得したり子 Actor を生成・参照したりなど各種 Actor に関する操作を行うために使用できます。
(3) Message が Print だった場合は、最後に Behaviors.same を返しています。これは Actor の状態を変更せずそのままその Actor を再利用するということを意味します。
Actor 本体は関数として定義しているためプロパティを持たせることはできません。その代わりに新しいパラメータで関数を再帰的に呼び出すことで Actor の状態を更新していくことができるという仕組みになっているのです。このように Actor としては従来通りステートフルでありながらコードの表現上は Immutable に記述することができます。
Classic Actor から移行する
ありがたいことに Typed Actor は Classic Actor と共存できるように作られています。そのため Typed Actor を部分的に導入したり段階的に移行したりといったことが可能です。
Classic な ActorSystem から Typed Actor を生成する方法は極めて簡単です。
akka.actor.typed.scaladsl.adapter._ というアダプターを import すると ActorSystem から spawn メソッドで Typed Actor が生成できるようになります。生成した Actor はそのまま普通に Typed Actor として使用できます。
これにより、既存の Classic Actor ベースのコードにほとんど手を加えることなく、新規に追加するコードは Typed Actor で記述するといったことが可能になります。Classic Actor と Typed Actor では記述方法がだいぶ異なってくるため、一気に移行するよりもそのようにまずは新規追加のコードから導入して少しずつ移行していくのがよいのではと思います。
逆に Typed Actor の環境から Classic Actor を生成して扱うこともできます。このあたりの相互運用については以下の公式ドキュメントで具体的に説明されています。