Submit Search
Upload
すべてのアクター プログラマーが知るべき 単一責務原則とは何か
•
10 likes
•
2,573 views
T
TanUkkii
Follow
Reactive Shinjuku meetup #2 LT
Read less
Read more
Engineering
Report
Share
Report
Share
1 of 22
Download now
Download to read offline
Recommended
ハトでもわかる単純パーセプトロン
ハトでもわかる単純パーセプトロン
takosumipasta
単純パーセプトロン
単純パーセプトロン
T2C_
アクターモデルについて
アクターモデルについて
Takamasa Mitsuji
Distributed ID generator in ChatWork
Distributed ID generator in ChatWork
TanUkkii
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
TanUkkii
Architecture of Falcon, a new chat messaging backend system build on Scala
Architecture of Falcon, a new chat messaging backend system build on Scala
TanUkkii
JSON CRDT
JSON CRDT
TanUkkii
Akka Clusterの耐障害設計
Akka Clusterの耐障害設計
TanUkkii
Recommended
ハトでもわかる単純パーセプトロン
ハトでもわかる単純パーセプトロン
takosumipasta
単純パーセプトロン
単純パーセプトロン
T2C_
アクターモデルについて
アクターモデルについて
Takamasa Mitsuji
Distributed ID generator in ChatWork
Distributed ID generator in ChatWork
TanUkkii
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
TanUkkii
Architecture of Falcon, a new chat messaging backend system build on Scala
Architecture of Falcon, a new chat messaging backend system build on Scala
TanUkkii
JSON CRDT
JSON CRDT
TanUkkii
Akka Clusterの耐障害設計
Akka Clusterの耐障害設計
TanUkkii
WaveNet
WaveNet
TanUkkii
スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成
TanUkkii
Akka HTTP
Akka HTTP
TanUkkii
ディープニューラルネット入門
ディープニューラルネット入門
TanUkkii
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
TanUkkii
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
TanUkkii
Isomorphic web development with scala and scala.js
Isomorphic web development with scala and scala.js
TanUkkii
Scalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリング
TanUkkii
ECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミング
TanUkkii
プログラミング言語Scala
プログラミング言語Scala
TanUkkii
これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6
TanUkkii
More Related Content
More from TanUkkii
WaveNet
WaveNet
TanUkkii
スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成
TanUkkii
Akka HTTP
Akka HTTP
TanUkkii
ディープニューラルネット入門
ディープニューラルネット入門
TanUkkii
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
TanUkkii
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
TanUkkii
Isomorphic web development with scala and scala.js
Isomorphic web development with scala and scala.js
TanUkkii
Scalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリング
TanUkkii
ECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミング
TanUkkii
プログラミング言語Scala
プログラミング言語Scala
TanUkkii
これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6
TanUkkii
More from TanUkkii
(11)
WaveNet
WaveNet
スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成
Akka HTTP
Akka HTTP
ディープニューラルネット入門
ディープニューラルネット入門
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
Isomorphic web development with scala and scala.js
Isomorphic web development with scala and scala.js
Scalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリング
ECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミング
プログラミング言語Scala
プログラミング言語Scala
これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6
すべてのアクター プログラマーが知るべき 単一責務原則とは何か
1.
すべてのアクター プログラマーが知るべき 単一責務原則とは何か 安田裕介 Reactive Shinjuku meetup
#2 LT
2.
自己紹介 • Twitter: @TanUkkii007 •
Akka大好き
3.
アクターは一つの責務に特化すべき AnactormustbespecializedtojustONE responsibility http://www.slideshare.net/ktoso/zen-of-akka ScalaMatsuri 2016 Zen of
Akka" by @ktosopl
4.
責務が多いアクターの問題 • 責務の拡大とともに処理が複雑化する • 責務の拡大とともに起きうる障害がついてくる •
並列・分散して処理することが困難になる
5.
アクターの責務が多い場合に コードに表れる兆候 1.receive関数のcase節が長い 2.メッセージが汎化しすぎている 3.1つのアクターが担う処理が多すぎる 4.親アクター直下の子アクターが多い これらの問題点を把握し、単一責務に改善していきましょう!
6.
1.receive関数のcase節が長い class SequentialComputationActor(replyTo: ActorRef,
settings: Settings) extends Actor with Computation1 with Computation2 with Computation3 with Computation4 with Computation5 { def receive: Receive = { case Request(x0) => { val x1 = compute1(x0, settings) val x2 = compute2(x1, settings) val x3 = compute3(x2, settings) val x4 = compute4(x3, settings) val x5 = compute5(x4, settings) replyTo ! Result(x5) } } } 例:compute1 - compute5までの計算をして結果を返す • 長い • 今後機能を追加するたびに長くなる 問題点:1つのcase節で多くのことをしすぎている
7.
class SequentialComputationActor(replyTo: ActorRef,
settings: Settings) extends Actor with Computation1 with Computation2 with Computation3 with Computation4 with Computation5 { import SequentialComputationActorProtocol._ def receive: Receive = { case Request(x0) => self ! ComputeRequest1(x0) case ComputeRequest1(x1) => self ! ComputeResult1(compute1(x1, settings)) case ComputeResult1(x2) => self ! ComputeResult2(compute2(x2, settings)) case ComputeResult2(x3) => self ! ComputeResult3(compute3(x3, settings)) case ComputeResult3(x4) => self ! ComputeResult4(compute4(x4, settings)) case ComputeResult4(x5) => replyTo ! ComputeResult(compute5(x5, settings)) } } 解決策:自分にメッセージを投げて複数の段階で処理する 1.receive関数のcase節が長い
8.
効果 • case節が単純になり、各段階で考慮すべきスコープが狭まり保守性 が向上した • 処理を分割したことで、スレッドを専有しなくなり、その間他の処 理に回すことができる •
→スループットが向上する • →CPU使用率が向上する • どのように実行するかDispatcherで調整可能になる (parallelism • -**, throuput etc.)
9.
2.メッセージが汎化しすぎている class SequentialComputationActor extends
Actor with Computation1 with Computation2 with Computation3 with Computation4 with Computation5 { def receive: Receive = { case RequestWithSettings(x0, settings) => { val x1 = compute1(x0, settings) val x2 = compute2(x1, settings) val x3 = compute3(x2, settings) val x4 = compute4(x3, settings) val x5 = compute5(x4, settings) sender() ! Result(x5) } } } コンストラクタではなくメッセージに settingsを乗せて、動的に変えられるよう 汎用化している sender()を使って返信先を動的に変える • 前述のような自分にメッセージを送り複数の段階で処理することができなくなった (settingsと sender()が別のcase節では参照できないため) • → だからcase節が長くなる • 子アクター化による集約ができない(後述) 問題: コンストラクタではなくメッセージで依存する情報を取得するとどうだろう?
10.
class SequentialComputationActor extends
Actor with Computation1 with Computation2 with Computation3 with Computation4 with Computation5 { import SequentialComputationActorProtocol._ def receive: Receive = { case RequestWithSettings(x0, settings) => { context.become(computing(sender(), settings)) self forward ComputeRequest1(x0) } } def computing(replyTo: ActorRef, settings: Settings): Receive = { case ComputeRequest1(x1) => self ! ComputeResult1(compute1(x1, settings)) case ComputeResult1(x2) => self ! ComputeResult2(compute2(x2, settings)) case ComputeResult2(x3) => self ! ComputeResult3(compute3(x3, settings)) case ComputeResult3(x4) => self ! ComputeResult4(compute4(x4, settings)) case ComputeResult4(x5) => replyTo ! ComputeResult(compute5(x5, settings)) } } 1. メッセージで依存する情報を取得せず、アクター初期化時に取得する 2. context.becomeで振る舞いを変更し依存する情報を固定する 解決策: settingsと返信先のActorRefがすべてのcase節から参照可能になった 2.メッセージが汎化しすぎている
11.
コンストラクタで初期化時に依存する 情報を取得したほうが並列化できる val sequentialComputationActor =
system.actorOf(SequentialComputationActor.props()) computeRequests.foreach { request => sequentialComputationActor ! RequestWithSettings(request.x, request.settings) } computeRequests.foreach { request => val sequentialComputationActor = system.actorOf(SequentialComputationActor.props(probe.ref, request.settings)) sequentialComputationActor ! Request(request.x) } • 依存する情報をメッセージから動的に取得できるので1つのアクターで複数の 場合を処理できる。 • ただし各メッセージを並列には処理できない • ※context.becomeは使わないほうがよい • 依存する情報をアクターの初期化時に取得するのでメッセージごとにアクター を作る必要がある • 各メッセージを並列に処理できる • ※処理し終えたアクターを殺すことを忘れずに
12.
教訓 • メッセージに情報を乗せて汎用的にすると様々な問題が発生する • case節の膨張 •
処理を分割する際に振る舞いの変更を伴う • 並列化をしにくい • メッセージに乗せる動的な情報は絞り、特定の用途に特化させる • アクター初期化時に依存する情報を決定し、特定の処理に特化さ せる
13.
3.アクターが担う処理が多すぎる class SequentialComputationActor(replyTo: ActorRef,
settings: Settings) extends Actor with Computation1 with Computation2 with Computation3 with Computation4 with Computation5 { import SequentialComputationActorProtocol._ def receive: Receive = { case Request(x0) => self ! ComputeRequest1(x0) case ComputeRequest1(x1) => self ! ComputeResult1(compute1(x1, settings)) case ComputeResult1(x2) => self ! ComputeResult2(compute2(x2, settings)) case ComputeResult2(x3) => self ! ComputeResult3(compute3(x3, settings)) case ComputeResult3(x4) => self ! ComputeResult4(compute4(x4, settings)) case ComputeResult4(x5) => replyTo ! ComputeResult(compute5(x5, settings)) } } • ミックスインしているtraitが多すぎる • それに起因する例外も多くなる • →ライフサイクルが複雑になる (preRestart etc.) • →SupervisorStrategyが複雑になる 問題
14.
class Compute1Actor(replyTo: ActorRef,
settings: Settings) extends Actor with Computation1 { def receive: Receive = { case ComputeRequest1(x1) => replyTo ! ComputeResult1(compute1(x1, settings)) } override def preRestart(reason: Throwable, message: Option[Any]) = { replyTo ! ComputeResult1(defaultResult) } } object Compute1Actor { def props(replyTo: ActorRef, settings: Settings): Props = Props(new Compute1Actor(replyTo, settings)) } 解決策:子アクターに処理を封じ込める 3.アクターが担う処理が多すぎる
15.
class SequentialComputationActor(replyTo: ActorRef,
settings: Settings) extends Actor { import SequentialComputationActorProtocol._ val compute1Actor = context.actorOf(Compute1Actor.props(self, settings)) val compute2Actor = context.actorOf(Compute2Actor.props(self, settings)) val compute3Actor = context.actorOf(Compute3Actor.props(self, settings)) val compute4Actor = context.actorOf(Compute4Actor.props(self, settings)) val compute5Actor = context.actorOf(Compute5Actor.props(self, settings)) def receive: Receive = { case Request(x0) => compute1Actor forward ComputeRequest1(x0) case ComputeResult1(x1) => compute2Actor ! ComputeRequest2(x1) case ComputeResult2(x2) => compute3Actor ! ComputeRequest3(x2) case ComputeResult3(x3) => compute4Actor ! ComputeRequest4(x3) case ComputeResult4(x4) => compute5Actor ! ComputeRequest5(x4) case ComputeResult5(x5) => replyTo ! ComputeResult(x5) } } • トレイトがなくなった • 親アクターは実際の処理はしない • 子アクターを集約しメッセージの制御のみを行う 3.アクターが担う処理が多すぎる
16.
効果 • 親アクターが果たす機能は今までと変わらない • 親アクターの責務は今までよりも縮小:メッセージ の制御だけ •
親アクターの障害は限定的:子アクターが対処でき ずEscaleteされるもののみ
17.
4.親アクター直下の子アクターが多い class SequentialComputationActor(replyTo: ActorRef,
settings: Settings) extends Actor { import SequentialComputationActorProtocol._ val compute1Actor = context.actorOf(Compute1Actor.props(self, settings)) val compute2Actor = context.actorOf(Compute2Actor.props(self, settings)) val compute3Actor = context.actorOf(Compute3Actor.props(self, settings)) val compute4Actor = context.actorOf(Compute4Actor.props(self, settings)) val compute5Actor = context.actorOf(Compute5Actor.props(self, settings)) def receive: Receive = { case Request(x0) => compute1Actor forward ComputeRequest1(x0) case ComputeResult1(x1) => compute2Actor ! ComputeRequest2(x1) case ComputeResult2(x2) => compute3Actor ! ComputeRequest3(x2) case ComputeResult3(x3) => compute4Actor ! ComputeRequest4(x3) case ComputeResult4(x4) => compute5Actor ! ComputeRequest5(x4) case ComputeResult5(x5) => replyTo ! ComputeResult(x5) } } 問題:子アクターが多いとメッセージの制御が複雑になり 親アクターのreceive関数が大きくなる
18.
解決策:アクターヒエラルキー の階層を増やす ¦-SequentialComputationActor ¦-Compute1Actor ¦-Compute2Actor ¦-Compute3Actor ¦-Compute4Actor ¦-Compute5Actor ¦-SequentialComputationActor ¦-Compute123Actor ¦-Compute1Actor ¦-Compute2Actor ¦-Compute3Actor ¦-Compute4Actor ¦-Compute5Actor before after 例としてcompute1,compute2,compute3に意味のある単位を見出した場合、 それらをまとめる親アクターを作る
19.
class Compute123Actor(replyTo: ActorRef,
settings: Settings) extends Actor { import Compute123ActorProtocol._ import SequentialComputationActorProtocol._ val compute1Actor = context.actorOf(Compute1Actor.props(self, settings)) val compute2Actor = context.actorOf(Compute2Actor.props(self, settings)) val compute3Actor = context.actorOf(Compute3Actor.props(self, settings)) def receive: Receive = { case Compute123Request(x0) => compute1Actor forward ComputeRequest1(x0) case ComputeResult1(x) => compute2Actor forward ComputeRequest2(x) case ComputeResult2(x) => compute3Actor forward ComputeRequest3(x) case ComputeResult3(x) => replyTo ! Compute123Result(x) } } Compute1Actor, Compute2Actor, Compute3Actorを まとめる中間アクターをつくる 4.親アクター直下の子アクターが多い
20.
class SequentialComputationActor(replyTo: ActorRef,
settings: Settings) extends Actor { import SequentialComputationActorProtocol._ import Compute123ActorProtocol._ val compute123Actor = context.actorOf(Compute123Actor.props(self, settings)) val compute4Actor = context.actorOf(Compute4Actor.props(self, settings)) val compute5Actor = context.actorOf(Compute5Actor.props(self, settings)) def receive: Receive = { case Request(x0) => compute123Actor forward Compute123Request(x0) case Compute123Result(x3) => compute4Actor ! ComputeRequest4(x3) case ComputeResult4(x4) => compute5Actor ! ComputeRequest5(x4) case ComputeResult5(x5) => replyTo ! ComputeResult(x5) } } • 直下の子アクターが少なくなった • receive関数が小さく単純になった 4.親アクター直下の子アクターが多い
21.
効果 • 親は直下の子アクターの障害とメッセージを制御す るだけでよい • 直下の子の数を減らせば親の責務が減る •
receive関数を小さくできる • 直下の子の数を減らしつつ、ヒエラルキーを深くす ることで責務を減らしつつ機能は保つことができる
22.
まとめ アクタープログラミングの設計手法は 純粋関数型プログラミングよりも簡単!怖くない!
Download now