Akka Typed - 型安全にメッセージパッシングする! / Akka Typed - typesafe messaging

2b3d68169a23966b99cfc481e40e6593?s=47 hayasshi
November 10, 2018

Akka Typed - 型安全にメッセージパッシングする! / Akka Typed - typesafe messaging

2b3d68169a23966b99cfc481e40e6593?s=128

hayasshi

November 10, 2018
Tweet

Transcript

  1. 2.

    自己紹介 林 大介 Twitter: @hayasshi_ GitHub: hayasshi ChatWork 株式会社 Scala

    2.9 系から使い始め(2013 年頃) 型好き、Akka 好き 2018-11-10 Scala 関西Summit 2018 2
  2. 12.

    Akka Actors akka-scala-seed.g8 class Greeter(message: String, printerActor: ActorRef) extends Actor

    { import Greeter._ import Printer._ var greeting = "" def receive: Receive = { case WhoToGreet(who) => greeting = message + ", " + who case Greet => printerActor ! Greeting(greeting) } } 2018-11-10 Scala 関西Summit 2018 12
  3. 13.

    Akka Actors val system: ActorSystem = ActorSystem("helloAkka") val printer: ActorRef

    = system.actorOf(Printer.props, "printerActor") val howdyGreeter: ActorRef = system.actorOf(Greeter.props("Howdy", printer), "howdyGreeter") val goodDayGreeter: ActorRef = system.actorOf(Greeter.props("Good day", printer), "goodDayGreeter") howdyGreeter ! WhoToGreet("Akka") howdyGreeter ! Greet // Howdy, Akka howdyGreeter ! WhoToGreet("Scala") howdyGreeter ! Greet // Howdy, Scala goodDayGreeter ! WhoToGreet("Play") goodDayGreeter ! Greet // Good day, Play 2018-11-10 Scala 関西Summit 2018 13
  4. 15.

    Akka Actors val system: ActorSystem = ActorSystem("helloAkka") val printer: ActorRef

    = system.actorOf(Printer.props, "printerActor") val howdyGreeter: ActorRef = system.actorOf(Greeter.props("Howdy", printer), "howdyGreeter") val goodDayGreeter: ActorRef = system.actorOf(Greeter.props("Good day", printer), "goodDayGreeter") howdyGreeter ! Greeting("Akka") howdyGreeter ! Greeting("Scala") goodDayGreeter ! Greeting("Play") なんか動きそう。コンパイルも通る。でも動かない... 2018-11-10 Scala 関西Summit 2018 15
  5. 16.

    tell ( ! ) メソッド型安全じゃない問題 trait ScalaActorRef { ref: ActorRef

    ⇒ def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit } type Receive = PartialFunction[Any, Unit] trait Actor { def receive: Receive } アクター定義時に指定していないメッセージを受け取れてしまう ( コンパイルできてしまう) コードベースが大規模になってくると間違ったメッセージを送って しまう可能性が高くなる 2018-11-10 Scala 関西Summit 2018 16
  6. 19.

    Akka Typed Akka Actors のメッセージパッシングを 型安全に行うためのモジュール Akka Actors の置き換えになる 基本的な部分のAPI

    はだいたい固まっている 他モジュールとの統合API の部分を中心に細かな変更が続いている 2018-11-10 Scala 関西Summit 2018 19
  7. 20.

    Akka Typed libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion アクターの定義は、振る舞い

    Behavior を定義する 振る舞いは、次の振る舞いを返す入れ子構造になっている import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.{ActorContext, Behaviors} case class Greet(whom: String) val greeterBehavior: Behavior[Greet] = Behaviors.receive { (ctx: ActorContext[Greet], msg) => ctx.log.info("Hello {}!", msg.whom) Behaviors.same } 2018-11-10 Scala 関西Summit 2018 20
  8. 21.

    Akka Typed アクターは振る舞い Behavior から生成される import akka.actor.typed.ActorSystem val greeter =

    ActorSystem(greeterBehavior, "greeter") greeter ! Greet("Akka") // Hello Akka! case class Greeting(whom: String) greeter ! Greeting("Akka") // compile error ( ActorSystem もActorRef[T] を継承している) 型安全にメッセージパッシングできる! 2018-11-10 Scala 関西Summit 2018 21
  9. 22.

    Akka Typed - 状態 def counterBehavior(current: Int): Behavior[Int] = Behaviors.receiveMessage

    { c => val count = current + c println(count) counterBehavior(count) } val counter = ActorSystem(counterBehavior(0), "counter") counter ! 1 // 1 counter ! 3 // 4 状態は再帰をつかって表現する よりイミュータブルな振る舞いとして扱うことができる 2018-11-10 Scala 関西Summit 2018 22
  10. 24.

    注意 これからのサンプルコードは資料の都合上、好ましくない書き方 をしているコードがあります 特に Akka Actors で Props を利用側が直接使っている箇所は 注意してください

    Props にインスタンスを利用側から渡すと、利用側でインスタン スの状態を書き換えることができてしまいます // ダメゼッタイ val actor = new Greeter(...) val props = Props(actor) val actorRef = system.actorOf(props, "child") actor.greeting = "hoge" 2018-11-10 Scala 関西Summit 2018 24
  11. 26.

    アクター生成とAsk - Akka Actors class Producer extends Actor { def

    receive: Receive = { case p: Props => val child = context.actorOf(p) context.watch(child) sender() ! context.actorOf(p) } } class Printer extends Actor { def receive: Receive = { case s: String => println(s) } } 2018-11-10 Scala 関西Summit 2018 26
  12. 27.

    アクター生成とAsk - Akka Actors val system = ActorSystem() val producer

    = system.actorOf(Props(classOf[Producer])) import akka.pattern.ask implicit val ec = system.dispatcher implicit val timeout: Timeout = Timeout(3.seconds) val printer: Future[ActorRef] = (producer ? Props(classOf[Printer])).mapTo[ActorRef] for (printerRef <- printer) { printerRef ! "Hello" } 2018-11-10 Scala 関西Summit 2018 27
  13. 28.

    アクター生成とAsk - Akka Actors アクター生成 ActorSystem.actorOf , ActorContext.actorOf アクターの内部どこでも生成することができる Ask

    import akka.pattern.ask で ask メソッド( ? ) を使えるようになる ask メソッドの返り値は Future[Any] のため mapTo をつかい、型を変換する ( 返ってくる実際の型と異なれば実行時エラー) 28
  14. 29.

    アクター生成とAsk - Akka Typed val printerBehavior: Behavior[String] = Behaviors.receiveMessage {

    msg => println(msg) Behaviors.same } val producerBehavior: Behavior[SpawnProtocol] = Behaviors.receive { (ctx, msg) => msg match { case Spawn(bhvr, name, props, replyTo) => val ref = ctx.spawn(bhvr, name, props) ctx.watch(ref) replyTo ! ref } Behaviors.same } 2018-11-10 Scala 関西Summit 2018 29
  15. 30.

    アクター生成とAsk - Akka Typed アクター生成 ActorContext[T].spawn 振る舞いのなか( コンテキストのあるスコープ) で 生成することができる

    SpawnProtocol はアクター生成するための組み込みメッセージ 対応する振る舞いも標準で用意されている SpawnProtocol.behavior 2018-11-10 Scala 関西Summit 2018 30
  16. 31.

    アクター生成とAsk - Akka Typed val producerBehavior: Behavior[SpawnProtocol] = Behaviors.receive {

    (ctx, msg) => msg match { case Spawn(bhvr, name, props, replyTo) => val ref = ctx.spawn(bhvr, name, props) ctx.watch(ref) replyTo ! ref } Behaviors.same } val spawnBehavior = SpawnProtocol.behavior val producer = ActorSystem(spawnBehavior, "producer") ただし ActorContext[T].watch を差し込めない 2018-11-10 Scala 関西Summit 2018 31
  17. 32.

    アクター生成とAsk - Akka Typed val producer = ActorSystem(producerBehavior, "producer") import

    akka.actor.typed.scaladsl.AskPattern._ implicit val ec = producer.executionContext implicit val timeout: Timeout = Timeout(3.seconds) implicit val scheduler: Scheduler = producer.scheduler val printer: Future[ActorRef[String]] = producer ? SpawnProtocol.Spawn(printerBehavior, "printer") for (printerRef <- printer) { printerRef ! "Hello" } 2018-11-10 Scala 関西Summit 2018 32
  18. 33.

    アクター生成とAsk - Akka Typed Ask import akka.actor.typed.scaladsl.AskPattern._ で、 ask メソッド(

    ? ) を使えるようになる ActorRef[T] から実際の型が返る def ?[U](replyTo: ActorRef[U] ⇒T)(implicit...): Future[U] 戻し先となる ActorRef[U] をメッセージに含める case class Request(s: String, replyTo: ActorRef[Response]) server ?(ref => Request("data", ref) server ? (Request("data", _)) 2018-11-10 Scala 関西Summit 2018 33
  19. 34.

    アクター生成とAsk - 比較まとめ アクター生成 大きな違いはない Typed は ActorSystem ( ヒエラルキートップ)

    からの 子アクター生成がメッセージ経由になる Ask 返ってくるメッセージに型がある! ask メソッドで送るメッセージに戻り先の ActorRef[U] を含める必要がある ( 送り先のActorRef[T] も受け付けれる必要がある) 2018-11-10 Scala 関西Summit 2018 34
  20. 37.

    スーパービジョン - Akka Actors class Supervisor extends Actor { override

    val supervisorStrategy: SupervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1.minute) { case _: ArithmeticException ⇒ Resume case _: NullPointerException ⇒ Restart case _: IllegalArgumentException ⇒ Stop case _: Exception ⇒ Escalate } def receive: Receive = { case p: Props ⇒ sender() ! context.actorOf(p) } } class Child extends Actor { def receive: Receive = { case ex: Exception ⇒ throw ex 37
  21. 38.

    スーパービジョン - Akka Typed val childBehavior: Behavior[Exception] = Behaviors.receiveMessage(throw _)

    val supervisorBehavior: Behavior[Exception] = Behaviors.supervise { Behaviors.supervise { Behaviors.supervise { childBehavior }.onFailure[ArithmeticException](SupervisorStrategy.resume) }.onFailure[NullPointerException]( SupervisorStrategy.restartWithLimit( maxNrOfRetries = 10, withinTimeRange = 1.minute ) ) }.onFailure[IllegalArgumentException](SupervisorStrategy.stop) 2018-11-10 Scala 関西Summit 2018 38
  22. 39.

    スーパービジョン Actors Typed 戦略定義位置 親アクター Behavior への組み込み 戦略種別 OneForOneStrategy AllForOneStrategy

    OneForOneStrategy 対応内容種別 Resume , Restart , Stop Escalate Resume , Restart , Stop 再起動制御 あり あり デフォルトの戦略 OneForOneStrategy で Restart OneForOneStrategy で Stop 2018-11-10 Scala 関西Summit 2018 39
  23. 43.

    ライフサイクル - Akka Actors class IdPrinterActor extends Actor { override

    def preStart(): Unit = { println(s"preStart ${self.path}") super.preStart() } override def preRestart(reason: Throwable, message: Option[Any]): Unit = { println(s"preRestart $message ${self.path}") super.preRestart(reason, message) } override def postRestart(reason: Throwable): Unit = { println(s"postRestart ${reason.getMessage} ${self.path}") super.postRestart(reason) } override def postStop(): Unit = { println(s"postStop ${self.path}") super.postStop() } 2018-11-10 Scala 関西Summit 2018 43
  24. 44.

    ライフサイクル - Akka Actors class IdPrinterActor extends Actor { //

    ...lifecycle hook override def receive: Receive = { case Message(id) => id match { case 0L => throw new Exception("Id is zero!") case 9L => context.stop(self) case _ => println(id) } } } 2018-11-10 Scala 関西Summit 2018 44
  25. 45.

    ライフサイクル - Akka Actors object IdPrinterActor { def name(id: Long):

    String = s"id-printer-$id" def props: Props = Props(classOf[IdPrinterActor]) } val system = ActorSystem() val printer1 = system.actorOf(IdPrinterActor.props, IdPrinterActor.name(1L)) printer1 ! Message(1L) val printer0 = system.actorOf(IdPrinterActor.props, IdPrinterActor.name(0L)) printer0 ! Message(0L) val printer9 = system.actorOf(IdPrinterActor.props, IdPrinterActor.name(9L)) printer9 ! Message(9L) 2018-11-10 Scala 関西Summit 2018 45
  26. 46.

    ライフサイクル - Akka Actors 見やすいように調整しています preStart akka://default/user/id-printer-1 1 preStart akka://default/user/id-printer-0

    [ERROR] [11/08/2018 23:12:35.727] [default-akka.actor.default-dispatcher-4] [akka: java.lang.Exception: Id is zero! at ... preRestart Some(Message(0)) akka://default/user/id-printer-0 postStop akka://default/user/id-printer-0 postRestart Id is zero! akka://default/user/id-printer-0 preStart akka://default/user/id-printer-0 preStart akka://default/user/id-printer-9 postStop akka://default/user/id-printer-9 2018-11-10 Scala 関西Summit 2018 46
  27. 47.

    ライフサイクル - Akka Typed val idPrintBehavior: Behavior[Message] = Behaviors.supervise {

    Behaviors.setup[Message] { ctx => println(s"start ${ctx.self.path}") Behaviors.receiveMessage[Message] { msg => msg.id match { case 0L => throw new Exception("Id is zero!") case 9L => Behaviors.stopped case _ => println(msg.id);Behaviors.same } }.receiveSignal { case (context, PreRestart) => println(s"preRestart ${context.self.path}");Behaviors.same case (context, PostStop) => println(s"postStop ${context.self.path}");Behaviors.same } } }.onFailure(SupervisorStrategy.restart) 2018-11-10 Scala 関西Summit 2018 47
  28. 48.

    ライフサイクル - Akka Typed def idPrinterName(id: Long): String = s"id-printer-${id.toString}"

    val system = ActorSystem(SpawnProtocol.behavior, "lifecycle") import akka.actor.typed.scaladsl.AskPattern._ implicit val ec: ExecutionContext = system.executionContext implicit val timeout: Timeout = Timeout(3.seconds) implicit val scheduler: Scheduler = system.scheduler for { printer1 <- system ? SpawnProtocol.Spawn(idPrintBehavior, idPrinterName(1L)) printer0 <- system ? SpawnProtocol.Spawn(idPrintBehavior, idPrinterName(0L)) printer9 <- system ? SpawnProtocol.Spawn(idPrintBehavior, idPrinterName(9L)) } { printer1 ! Message(1L) printer0 ! Message(0L) printer9 ! Message(9L) } 2018-11-10 Scala 関西Summit 2018 48
  29. 49.

    ライフサイクル - Akka Typed 見やすいように調整しています setup akka://lifecycle/user/id-printer-1 1 setup akka://lifecycle/user/id-printer-0

    preRestart akka://lifecycle/user/id-printer-0 setup akka://lifecycle/user/id-printer-0 [ERROR] [11/08/2018 23:51:35.515] [lifecycle-akka.actor.default-dispatcher-2] [ java.lang.Exception: Id is zero! at ... setup akka://lifecycle/user/id-printer-9 postStop akka://lifecycle/user/id-printer-9 2018-11-10 Scala 関西Summit 2018 49
  30. 50.

    ライフサイクル Actors Typed フック定義 override して拡張 super 呼び出しが必須 receiveSignal にて

    Behavior を拡張 フック箇所の種類 preStart , postStop preRestart , postRestart PreRestart , PostStop 再起動時の フック箇所 preRestart postStop postRestart preStart PreRestart 2018-11-10 Scala 関西Summit 2018 50
  31. 52.

    他のモジュールとの統合 Akka Streams との統合用API を含むモジュールが提供されている libraryDependencies += "com.typesafe.akka" %% "akka-stream-typed"

    % akkaVersion ActorSource や ActorSink などのコンポーネントをつかい バッファや OverflowStrategy を設定して Akka Streams の Graph に Akka Actors を組み込むことができる 2018-11-10 Scala 関西Summit 2018 52
  32. 53.

    他のモジュールとの統合 その他のモジュールとの統合API も絶賛開発されていっています ( 主にクラスター関連) "com.typesafe.akka" %% "akka-cluster-typed" % akkaVersion

    "com.typesafe.akka" %% "akka-cluster-sharding-typed" % akkaVersion "com.typesafe.akka" %% "akka-persistence-typed" % akkaVersion 2018-11-10 Scala 関西Summit 2018 53
  33. 56.

    まとめ Akka Typed になり より型安全にコーディングができるようになった コーディングスタイルが大きく変わるので切り替えが必要 細かな部分で Akka Actors と挙動がことなる

    他のモジュールとの統合API も充実してきている アクタープログラミングの本質的な部分は変わっていない 2018-11-10 Scala 関西Summit 2018 56