Akka.NETでDIする

基本的には[公式のドキュメント](http://getakka.net/articles/actors/dependency-injection.html)が充実しているので、その内容通りです。
読むだけだとわからなかった箇所について試してみたメモ。

# 環境
以下の環境で試してみました。Akka.NETは.NET Coreでも使えるので素晴らしいですね。

* Visual Studio for Mac
* .NET Core 1.1

# 基本的な使い方
以下、DIコンテナとしてAutofacを使います。
DIコンテナへのRegisterはAkka関係なく通常通り。注意点は、Propsではなくアクター自身のクラスを登録することくらい。

``` Program.cs

    class Program
    {
        static void Main(string[] args)
        {
            using (var actorSystem = ActorSystem.Create("actor"))
            {
                var builder = new Autofac.ContainerBuilder();
                builder.Register(_ => new Worker()).InstancePerDependency();

                using(var container = builder.Build())
                {
                    InvokeActor(actorSystem, container);

                    Console.ReadKey();
                }
            }
        }
```

アクターの呼び出し側は以下の通り。DependencyResolverを生成した上で、Akka.DIが提供するActorSystemの拡張メソッド`DI()`を経由すると、RegisterされたアクターのPropsを取得できます。

``` Program.cs
        private static void InvokeActor(ActorSystem actorSystem, IContainer container)
        {
            var resolver = new AutoFacDependencyResolver(container, actorSystem);
            var actor = actorSystem.ActorOf(actorSystem.DI().Props<Worker>());
            actor.Tell("hello actor!");
        }
    }
```

ちなみにアクターの実装は以下の通り。特別なことは特になし。PersistentActorであることにも深い意味はありません。

``` Worker.cs
    class Worker : UntypedPersistentActor
    {
        public override string PersistenceId => "aaa";

        public Worker(int arg)
        {
            Console.WriteLine($"a constructor of {nameof(Worker)}");
        }
        protected override void OnCommand(object message)
        {
            Console.WriteLine($"received:{message}");
        }

        protected override void OnRecover(object message)
        {
            
        }

    }
```


出力はこんな感じ。ちゃんとWorkerが作られている様子。

```
a constructor of Worker
received:hello actor!
```

# Actorに引数を渡す
特に変わったことはなく、`builder.Register`に渡すファクトリで好きにやれる。

``` Program.cs
builder.Register(_ => new Worker(256)).InstancePerDependency();
```

`builder.Register`でProps生成用のファクトリをinjectionしている感じ。
一方で、`DI().Props<T>()`で引数を渡すことはできない？

# オブジェクトを差し替え可能にする

Actorの解決時に、型引数としてabstract classを指定しましょう。

``` Program.cs
builder.Register(_ => new Worker(256)).As<WorkerBase>().InstancePerDependency();
```

なお、このとき`Worker`は`WorkerBase`を継承している必要があります。

``` Worker.cs

    abstract class WorkerBase : UntypedPersistentActor 
    {
    }

    class Worker : WorkerBase
    {
         // 省略
    }
```
このようにすれば、InvokeActor内で生成するアクターは、WorkerBaseを継承した任意のクラスに差し替え可能です。

## できないこと

なお、このように関係ない型をinjectしようとすると実行時エラーが発生します。

``` NG1.cs
builder.Register(_ => new OtherWorker()).As<Worker>().InstancePerDependency();
```

また、`DI().Props<TActor>()`の型引数`TActor`は`ActorBase`を継承していることが求められるので、インタフェースを指定することはできません。

``` NG2.cs
builder.Register(_ => new OtherWorker()).As<IWorker>().InstancePerDependency();
```

#まとめ

最後に、最終的なコードを載せておきます。Main関数内の`builder.Register`を差し替えることで、`InvokeActor`に任意のアクターをInjectできます。便利ですね。


``` Program.cs
using System;
using Akka;
using Akka.Actor;
using Akka.DI.AutoFac;
using Akka.DI.Core;
using Akka.Persistence;
using Autofac;

namespace AkkaDITest
{
    class OtherWorker : UntypedPersistentActor
    {
        public override string PersistenceId => "iii";

        protected override void OnCommand(object message)
        {
            Console.WriteLine($"received in {nameof(OtherWorker)}:{message}");
        }

        protected override void OnRecover(object message)
        {
        }
    }

    abstract class WorkerBase : UntypedPersistentActor 
    {
    }

    class AltenativeWorker : WorkerBase 
    {
        public override string PersistenceId => "iii";

        public AltenativeWorker()
        {
            Console.WriteLine($"a constructor of {nameof(AltenativeWorker)}!");
        }
        protected override void OnCommand(object message)
        {
            Console.WriteLine($"received:{message}");
        }

        protected override void OnRecover(object message)
        {

        }
    }

    class Worker : WorkerBase
    {
        public override string PersistenceId => "aaa";
        private int Arg { get; }

        public Worker(int arg)
        {
            Console.WriteLine($"a constructor of {nameof(Worker)} with an argument {arg}!");
            this.Arg = arg;
        }
        protected override void OnCommand(object message)
        {
            Console.WriteLine($"received:{message} ({this.Arg})");
        }

        protected override void OnRecover(object message)
        {
            
        }

    }


    class Program
    {
        static void Main(string[] args)
        {
            using (var actorSystem = ActorSystem.Create("actor"))
            {
                var builder = new Autofac.ContainerBuilder();
                builder.Register(_ => new AltenativeWorker()).As<WorkerBase>().InstancePerDependency();
                //builder.Register(_ => new Worker(256)).As<WorkerBase>().InstancePerDependency();

                using(var container = builder.Build())
                {
                    InvokeActor(actorSystem, container);

                    Console.ReadKey();
                }
            }
        }

        private static void InvokeActor(ActorSystem actorSystem, IContainer container)
        {
            var resolver = new AutoFacDependencyResolver(container, actorSystem);
            var actor = actorSystem.ActorOf(actorSystem.DI().Props<WorkerBase>());
            actor.Tell("hello actor!");
        }
    }
}
```