VB.NETでもリアクティブしたい(Akka.NET)

はじめに

2018年は殺伐としたVB.NET界隈にAkka.NETの波が到来します。(しません)

という訳であまり需要がなさげなエントリですが*1、ライブラリ自体は結構成熟しているっぽいので実運用に突っ込んでもまぁ行けるんじゃないかなと思います。

Akkaとは

Akkaの概念自体については割と記事があるのでここでは割愛します。

インストールパッケージ

今回の記事で作成したプロジェクトでは以下のパッケージをNuGetでインストールします。

  • Akka(Akka本体)
  • Akka.Logger.Serilog(Serilogのログアダプタ)
  • Serilog(ロガー)
  • Serilog.Sinks.Console(Serilogのコンソールシンクの実装)

今回はロガーとしてSerilogを使用しますが、Serilog自体の説明は省略します。

メッセージとActorの実装

Akka.NETではActorはReceiveActor(もしくはUntypedActor)を継承して実装します。

ReceiveActorではコンストラクタ中で処理するメッセージの型とメッセージを処理するデリゲートを指定します。

Public Sub New(dest As IActorRef)
    _dest = dest
    Receive(Of String)(AddressOf ReceiveString)
    Receive(Of Greet)(AddressOf ReceiveGreet)
End Sub

また、メッセージは(Akkaに限らず)スレッド間を行き来するためイミュータブルになるように実装します。*2

本家のScalaではcase classで所望のクラスが簡単に量産できますが、VBではイミュータブルなクラスの定義がクッソ面倒*3なので弊社のプロダクト環境ではメタプログラミングとT4を駆使してコード生成をして対応しています。が本題ではないのでここでは触れません。

Public Class HelloActor
    Inherits ReceiveActor

    Private ReadOnly _logger As ILoggingAdapter = Context.GetLogger(New SerilogLogMessageFormatter())
    Private ReadOnly _dest As IActorRef

    Public Sub New(dest As IActorRef)
        _dest = dest
        Receive(Of String)(AddressOf ReceiveString)
        Receive(Of Greet)(AddressOf ReceiveGreet)
    End Sub

    Public Sub ReceiveString(msg As String)
        _dest.Tell(New Greet(msg))
    End Sub

    Public Sub ReceiveGreet(msg As Greet)
        _logger.Info("Returned {Message}", msg.Message)
    End Sub

End Class

Public Class EchoActor
    Inherits ReceiveActor

    Private ReadOnly _logger As ILoggingAdapter = Context.GetLogger(New SerilogLogMessageFormatter())

    Public Sub New()
        Receive(Of Greet)(AddressOf ReceiveGreet)
    End Sub

    Public Sub ReceiveGreet(msg As Greet)
        _logger.Info("Receive {Message}", msg.Message)
        Sender.Tell(New Greet($"You said {msg.Message}"))
    End Sub

End Class

Public Class Greet
    Public ReadOnly Property Message As String

    Public Sub New(msg As String)
        Message = msg
    End Sub

End Class

余談ですが、なんで

Receive(Of Greet)(AddressOf ReceiveGreet)

みたいな書き方をしているかなのですが、C#では

Receive<string>(message => {
  log.Info("Received String message: {0}", message);
  Sender.Tell(message);
});

みたいにラムダ式を使ってかなりきれいに書けますが、VBでは

Receive(Of Greet)(Sub(message)
                      _logger.Info("Returned {Message}", message.Message)
                  End Sub)

みたいになってお゛ぉ゛ぉ゛ぉ゛ぉ゛ぉ゛ぉ゛んってなるからです。 特に行数が10行を超えた時点でクッソ読みづらくなります。

ActorSystem

あとは本家と同じようにActorSystemを生成しーのActorを生成しーのトツギーノでリアクティブできます。

Module Program

    Dim conf As String = "
akka {
    loggers=[""Akka.Logger.Serilog.SerilogLogger, Akka.Logger.Serilog""]

    stdout-loglevel = off
    loglevel = DEBUG
    log-config-on-start = on
    actor {
        debug {
              receive = on 
              autoreceive = on
              lifecycle = on
              event-stream = on
              unhandled = on
        }
    }
}
"

    Sub Main()
        Dim logger = New LoggerConfiguration().
            WriteTo.Console().
            MinimumLevel.Debug().
            CreateLogger()
        Log.Logger = logger

        Dim system = ActorSystem.Create("vb-actor", conf)
        Dim echo = system.ActorOf(Of EchoActor)("echo")
        Dim hello = system.ActorOf(Props.Create(Function() New HelloActor(echo)))

        hello.Tell("hello")
        hello.Tell("hoge")

        Console.ReadLine()
        system.Terminate().Wait()
    End Sub

End Module
[23:09:14 INF] Receive hello
[23:09:14 INF] Receive hoge
[23:09:14 INF] Returned You said hello
[23:09:14 INF] Returned You said hoge

おわりに

Akka.NETはいかんせん日本語記事がほとんど存在しないので割と茨の道ですが、英語の記事自体は結構あるうえに本家のドキュメントも脳内で.NET向けにトランスパイルすれば割とそのまま適用できるので思ったよりは障壁は低いと思います。

なお、IntelliSenseの説明文がときたま「TBD」になっており、『はぁそうですか』ってなります。

f:id:jyuch:20180103233642p:plain

github.com

おわり

*1:執筆時点ではC#ですら日本語でヒットする記事は1件、VBに至っては英語ですらヒットしない

*2:マルチスレッドの大原則として不変な値は常にスレッドセーフというものがあります

*3:特にVB14以前