Created
February 3, 2017 04:47
-
-
Save DmitryBe/f905d1b2aa393fa939494a46bc382f44 to your computer and use it in GitHub Desktop.
scala akka dependency management using cake pattern
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import akka.actor.Status.Failure | |
import akka.actor.{Actor, ActorLogging, ActorRef, ActorRefFactory, ActorSystem, PoisonPill, Props} | |
import akka.dispatch.ExecutionContexts | |
import akka.testkit.{ImplicitSender, TestKit} | |
import org.scalatest._ | |
import scala.concurrent.duration._ | |
import akka.pattern.{ask, pipe} | |
import akka.util.Timeout | |
/* | |
configurations | |
*/ | |
trait TConfig { | |
val timeout = Timeout(5.seconds) | |
val globalExecContext = ExecutionContexts.global() | |
} | |
/* | |
logger | |
*/ | |
trait TLog { this: Actor => | |
def debug(msg: String) = println(msg) | |
} | |
/* | |
component example | |
*/ | |
trait TMathCmt { | |
val mathComponent: MathComponentImpl | |
class MathComponentImpl { | |
def add(a: Int, b: Int) = a + b | |
} | |
} | |
/* | |
master <-> worker protocol defined here | |
*/ | |
trait MasterWorkerProtocol { | |
// var 1: client -> parent -> child -> parent -> client | |
case class DoSomeAdd(a: Int, b: Int) | |
case class SomeMathResult(r: Int) | |
case class Add(a: Int, b: Int) | |
case class AddResult(r: Int) | |
// ------- | |
// var 2: client -> parent -> child -> client(res) && parent(operation completed) | |
case class DoSomeAdd2(a: Int, b: Int) | |
case class Add2(a: Int, b: Int, originSender: ActorRef) | |
case object OperationCompleted | |
} | |
/* | |
worker component | |
*/ | |
trait WorkerCmt { | |
// component dependency | |
this: MasterWorkerProtocol | |
with TMathCmt => | |
object Worker { | |
// def props() = Props(classOf[Child]) | |
def props() = Props(new Worker()) | |
} | |
class Worker | |
extends Actor | |
with TLog { | |
@scala.throws[Exception](classOf[Exception]) | |
override def preStart(): Unit = { | |
debug("child is starting") | |
} | |
@scala.throws[Exception](classOf[Exception]) | |
override def postStop(): Unit = { | |
debug("child is stopping") | |
} | |
def receive = { | |
case cmd: Add => | |
debug(s"child exec cmd and send result to sender(parent)") | |
cmd.a match { | |
case 0 => | |
debug("child simulate error") | |
sender ! akka.actor.Status.Failure(new Exception("error simulation")) | |
case _ => | |
val res = mathComponent.add(cmd.a, cmd.b) | |
sender ! AddResult(res) | |
} | |
case cmd: Add2 => | |
debug(s"child exec cmd and send result directly to client && inform parent about operation completed") | |
val res = mathComponent.add(cmd.a, cmd.b) | |
cmd.originSender ! AddResult(res) | |
context.parent ! OperationCompleted | |
} | |
} | |
} | |
/* | |
master component | |
*/ | |
trait MasterCmt { | |
// component dependency | |
this: MasterWorkerProtocol => | |
object Master{ | |
// def props(childMaker: ActorRefFactory => ActorRef) = Props(classOf[GenericDependentParent], childMaker) | |
def props(workerMaker: ActorRefFactory => ActorRef) = Props(new Master(workerMaker)) | |
} | |
class Master(childMaker: ActorRefFactory => ActorRef) | |
extends Actor | |
with TLog | |
with TConfig { | |
@scala.throws[Exception](classOf[Exception]) | |
override def preStart(): Unit = { | |
debug("parent is starting") | |
} | |
@scala.throws[Exception](classOf[Exception]) | |
override def postStop(): Unit = { | |
println("parent is stopping") | |
} | |
def receive = { | |
case cmd: DoSomeAdd => | |
debug("parent received cmd") | |
// implicits required by pipeTo | |
implicit val ec = globalExecContext | |
implicit val _timeout = timeout | |
debug("parent create child && send command") | |
val childActor = childMaker(context) | |
childActor ? Add(cmd.a, cmd.b) map { | |
case msg: AddResult => | |
debug("parent received response from child && send poison pill to child") | |
childActor ! PoisonPill | |
debug("parent send result back to client") | |
SomeMathResult(msg.r) | |
} recover { | |
case e: Exception => | |
debug(s"error from child: ${e.getMessage}") | |
debug("parent send poison pill to child") | |
childActor ! PoisonPill | |
debug("parent send failure msg to client") | |
akka.actor.Status.Failure(e) | |
} pipeTo sender | |
case cmd: DoSomeAdd2 => | |
debug("parent received cmd") | |
debug("parent create child && send command") | |
val childActor = childMaker(context) | |
childActor ! Add2(cmd.a, cmd.b, sender()) | |
case OperationCompleted => | |
debug("parent send poison pill to child") | |
sender ! PoisonPill | |
} | |
} | |
} | |
/* | |
app root && test | |
*/ | |
class DependencyExpSpec extends TestKit(ActorSystem("test1")) | |
with ImplicitSender | |
with WordSpecLike | |
with Matchers | |
with TMathCmt | |
with MasterWorkerProtocol | |
with WorkerCmt | |
with MasterCmt { | |
// wise dependency | |
override val mathComponent: MathComponentImpl = new MathComponentImpl() | |
"master-worker-component dependency test" must { | |
"case1: client -> master -> worker -> master -> client" in { | |
val maker = (ctx: ActorRefFactory) => ctx.actorOf(Worker.props()) | |
val parent = system.actorOf(Master.props(maker)) | |
parent ! DoSomeAdd(2,5) | |
expectMsg(SomeMathResult(7)) | |
assert(true) | |
} | |
"case2: client -> master -> worker -> client(direct reply) && master (operation complete info)" in { | |
val maker = (ctx: ActorRefFactory) => ctx.actorOf(Worker.props()) | |
val parent = system.actorOf(Master.props(maker)) | |
parent ! DoSomeAdd2(2,5) | |
expectMsg(AddResult(7)) | |
assert(true) | |
} | |
"case3: client -> master -> worker -> expected error" in { | |
val maker = (ctx: ActorRefFactory) => ctx.actorOf(Worker.props()) | |
val parent = system.actorOf(Master.props(maker)) | |
parent ! DoSomeAdd(0,5) | |
expectMsgPF(5.seconds) { | |
case msg: Failure => | |
/* expect actor to fail */ | |
} | |
assert(true) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
case 1 (out):
--> parent is starting
--> parent received cmd
--> parent create child && send command
--> child is starting
--> child exec cmd and send result to sender(parent)
--> parent received response from child && send poison pill to child
--> parent send result back to client
--> child is stopping