SlideShare a Scribd company logo
1 of 22
Download to read offline
すべてのアクター
プログラマーが知るべき
単一責務原則とは何か
安田裕介
Reactive Shinjuku meetup #2 LT
自己紹介
• Twitter: @TanUkkii007
• Akka大好き
アクターは一つの責務に特化すべき
AnactormustbespecializedtojustONE
responsibility
http://www.slideshare.net/ktoso/zen-of-akka
ScalaMatsuri 2016
Zen of Akka"
by @ktosopl
責務が多いアクターの問題
• 責務の拡大とともに処理が複雑化する
• 責務の拡大とともに起きうる障害がついてくる
• 並列・分散して処理することが困難になる
アクターの責務が多い場合に
コードに表れる兆候
1.receive関数のcase節が長い
2.メッセージが汎化しすぎている
3.1つのアクターが担う処理が多すぎる
4.親アクター直下の子アクターが多い
これらの問題点を把握し、単一責務に改善していきましょう!
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節で多くのことをしすぎている
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節が長い
効果
• case節が単純になり、各段階で考慮すべきスコープが狭まり保守性
が向上した
• 処理を分割したことで、スレッドを専有しなくなり、その間他の処
理に回すことができる
• →スループットが向上する
• →CPU使用率が向上する
• どのように実行するかDispatcherで調整可能になる (parallelism
• -**, throuput etc.)
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節が長くなる
• 子アクター化による集約ができない(後述)
問題:
コンストラクタではなくメッセージで依存する情報を取得するとどうだろう?
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.メッセージが汎化しすぎている
コンストラクタで初期化時に依存する
情報を取得したほうが並列化できる
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は使わないほうがよい
• 依存する情報をアクターの初期化時に取得するのでメッセージごとにアクター
を作る必要がある
• 各メッセージを並列に処理できる
• ※処理し終えたアクターを殺すことを忘れずに
教訓
• メッセージに情報を乗せて汎用的にすると様々な問題が発生する
• case節の膨張
• 処理を分割する際に振る舞いの変更を伴う
• 並列化をしにくい
• メッセージに乗せる動的な情報は絞り、特定の用途に特化させる
• アクター初期化時に依存する情報を決定し、特定の処理に特化さ
せる
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が複雑になる
問題
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.アクターが担う処理が多すぎる
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.アクターが担う処理が多すぎる
効果
• 親アクターが果たす機能は今までと変わらない
• 親アクターの責務は今までよりも縮小:メッセージ
の制御だけ
• 親アクターの障害は限定的:子アクターが対処でき
ずEscaleteされるもののみ
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関数が大きくなる
解決策:アクターヒエラルキー
の階層を増やす
¦-SequentialComputationActor
¦-Compute1Actor
¦-Compute2Actor
¦-Compute3Actor
¦-Compute4Actor
¦-Compute5Actor
¦-SequentialComputationActor
¦-Compute123Actor
¦-Compute1Actor
¦-Compute2Actor
¦-Compute3Actor
¦-Compute4Actor
¦-Compute5Actor
before after
例としてcompute1,compute2,compute3に意味のある単位を見出した場合、
それらをまとめる親アクターを作る
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.親アクター直下の子アクターが多い
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.親アクター直下の子アクターが多い
効果
• 親は直下の子アクターの障害とメッセージを制御す
るだけでよい
• 直下の子の数を減らせば親の責務が減る
• receive関数を小さくできる
• 直下の子の数を減らしつつ、ヒエラルキーを深くす
ることで責務を減らしつつ機能は保つことができる
まとめ
アクタープログラミングの設計手法は
純粋関数型プログラミングよりも簡単!怖くない!

More Related Content

More from TanUkkii

スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成TanUkkii
 
ディープニューラルネット入門
ディープニューラルネット入門ディープニューラルネット入門
ディープニューラルネット入門TanUkkii
 
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けープログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けーTanUkkii
 
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けープログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けーTanUkkii
 
Isomorphic web development with scala and scala.js
Isomorphic web development  with scala and scala.jsIsomorphic web development  with scala and scala.js
Isomorphic web development with scala and scala.jsTanUkkii
 
Scalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリングScalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリングTanUkkii
 
ECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミングECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミングTanUkkii
 
プログラミング言語Scala
プログラミング言語Scalaプログラミング言語Scala
プログラミング言語ScalaTanUkkii
 
これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6TanUkkii
 

More from TanUkkii (11)

WaveNet
WaveNetWaveNet
WaveNet
 
スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成
 
Akka HTTP
Akka HTTPAkka HTTP
Akka HTTP
 
ディープニューラルネット入門
ディープニューラルネット入門ディープニューラルネット入門
ディープニューラルネット入門
 
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けープログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
 
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けープログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
 
Isomorphic web development with scala and scala.js
Isomorphic web development  with scala and scala.jsIsomorphic web development  with scala and scala.js
Isomorphic web development with scala and scala.js
 
Scalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリングScalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリング
 
ECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミングECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミング
 
プログラミング言語Scala
プログラミング言語Scalaプログラミング言語Scala
プログラミング言語Scala
 
これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6
 

すべてのアクター プログラマーが知るべき 単一責務原則とは何か