Skip to content

Instantly share code, notes, and snippets.

@DmitryBe
Created February 3, 2017 04:47
Show Gist options
  • Save DmitryBe/f905d1b2aa393fa939494a46bc382f44 to your computer and use it in GitHub Desktop.
Save DmitryBe/f905d1b2aa393fa939494a46bc382f44 to your computer and use it in GitHub Desktop.
scala akka dependency management using cake pattern
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)
}
}
}
@DmitryBe
Copy link
Author

DmitryBe commented Feb 3, 2017

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment