AkkaのClassic ActorにGuiceを使ってDIする方法

Classic Actorを今時使っているところはあまり無いと思うが、現在携わっているプロジェクトでは既に多くのActorがClassicで書かれてしまっており、中々完全に移行できない。 このプロジェクトはPlay Frameworkで実装されているWebアプリでバッチ処理の定期実行にakka-quartz-schedulerを使っているため、ActorへのDIもなるべくGuiceでやりたい。 しかしこれまで開発していたメンバーがそのやり方がわからなかったのか、普通のコンストラクタインジェクションでDIが行われていた。 ただでさえActorは親子関係を持つことがほとんどであり、単なるコンストラクタインジェクションでは親から子にひたすら依存関係をバケツリレーしていくことになるので、新たな依存関係が増えると祖先のActorのコンストラクタを全て修正することになり、とてもしんどいので、方法を探すことにした。

結論としては、Classic Actor自体ではなく、Classic Actorを生成する際に必要になるPropsを生成するFactoryクラスを用意し、そのFactoryにDIすればよかった。

class ParentActor(childPropsFactory) extends Actor {
  override def receive: Receive = {
    case "Some Message" => context.actorOf(childPropsFactory.create)
  }
}

class ParentActorPropsFactory @Inject()(
  childPropsFactory: ChildPropsFactory,
) {
  def create: Props = Props(
    classOf[ParentActor],
    childPropsFactory,
  )
}

class ChildActor(foo: Foo, bar: Bar) extends Actor {
  override def receive: Receive = 
    case "Some Message" => ???
  }
}

class ChildActorPropsFactory @Inject()(
  foo: Foo,
  bar: Bar,
) {
  def create: Props = Props(
    classOf[ChildActor],
    foo,
    bar,
  )
}

これならば子孫で新たな依存関係が必要になっても、修正するのはそのActorのコンストラクタとPropsのFactoryの2つだけで済み、分かりやすい。

ただし、@Singletonアノテーションが付いたクラスには気をつける必要がある。 アクターモデルでは各Actorはなるべく何も共有しないことが望ましい。 しかし、ParentActorPropsFactoryやChildActorPropsFactoryを頂点とする依存関係のツリーに@Singletonが付いてるクラスが混じっていると、複数のActor間で1つのインスタンスを共有することになってしまう。

なお、Propsを生成するクラスを生成すること自体については、公式ドキュメントでPropsを生成するメソッドをActorのコンパニオンオブジェクトに持たせることが推奨されているので、それが別のクラスになったところで問題無いと思われる。