this question about design, for example now I have some process that load task list and try process each task, it looks like this:
import scala.concurrent.{ExecutionContext, Future}
case class TaskId(processorName: String, id: String)
case class TaskDetails(id: TaskId, status: String, json: String)
trait DAO {
def loadTaskIds(processorName: String): Future[Seq[TaskId]]
def loadTaskDetails(id: TaskId): Future[Option[TaskDetails]]
def deleteTask(id: TaskId): Future[Boolean]
def markSuccess(id: TaskId): Future[Boolean]
}
class Processor(name: String, dao: DAO, implicit val ec: ExecutionContext) {
def process(): Unit = {
dao
.loadTaskIds(name)
.map(_.map {
case taskId =>
dao.loadTaskDetails(taskId).flatMap {
case None =>
delete(taskId)
case Some(task) if task.status == "success" =>
delete(taskId)
case Some(task) =>
doProcess(task).flatMap {
case true =>
dao.markSuccess(taskId).map {
case true =>
Some(task)
case false =>
// log.error(...)
None
}
case false =>
// log.error(...)
Future.successful(None)
}
}
}).map {
case processingFutures =>
Future.sequence(processingFutures).map(_.flatten).map {
case completedTasks =>
// log.info(s"Processing '$name' tasks, complete [${completedTasks.map(_.id).mkString(", ")}]")
}
}
}
private def doProcess(task: TaskDetails): Future[Boolean] = ???
private def delete(taskId: TaskId): Future[Option[TaskDetails]] =
dao.deleteTask(taskId).map {
case true =>
None
case false =>
// log.error(...)
None
}
}
My question: how I need to change this code for getting 'True' Actor based application without using futures or ask pattern.
PS: I read that I need use only actor tell (!) but I can't understand how I can rewrite my logic and DAO as an actor.
PSS: my own approach
import akka.actor._
import scala.concurrent.duration._
object ExampleActorBoot extends App {
val system = ActorSystem("ExampleActorBoot")
val dao = system.actorOf(Props[DaoActor], "dao")
val processor = system.actorOf(Props(new ProcessorActor("test", dao)), "processor")
processor ! ProcessorActor.Process
}
case class TaskId(processorName: String, id: String)
case class TaskDetails(id: TaskId, status: String, json: String)
object DaoActor {
sealed trait DaoActorMessage {
def receiver: ActorRef
}
case class LoadTaskIds(processorName: String, receiver: ActorRef) extends DaoActorMessage
case class LoadTaskDetails(id: TaskId, receiver: ActorRef) extends DaoActorMessage
case class DeleteTask(id: TaskId, receiver: ActorRef) extends DaoActorMessage
case class MarkSuccess(id: TaskId, receiver: ActorRef) extends DaoActorMessage
}
class DaoActor extends Actor with ActorLogging {
import com.episodetracker.boot.DaoActor._
override def receive = {
case LoadTaskIds(processorName, receiver) =>
val result = 1 to 4 map {
case i =>
TaskId(processorName, i.toString)
}
receiver ! result
case LoadTaskDetails(id, receiver) =>
val result = TaskDetails(id, "pending", "JSON")
receiver ! result
case DeleteTask(id, receiver) =>
receiver ! true
case MarkSuccess(id, receiver) =>
receiver ! true
}
}
object ProcessorActor {
sealed trait ProcessorActorMessage
case object Process extends ProcessorActorMessage
private case class TaskComplete(id: TaskId, isSuccess: Boolean) extends ProcessorActorMessage
private case class ProcessingComplete(result: Seq[(TaskId, Boolean)]) extends ProcessorActorMessage
}
class ProcessorActor(name: String, dao: ActorRef) extends Actor with ActorLogging {
import com.episodetracker.boot.ProcessorActor._
override def receive = {
case Process =>
dao ! DaoActor.LoadTaskIds(name, context.actorOf(Props(new TasksProcessorResultActor())))
case ProcessingComplete(result) =>
sender() ! PoisonPill
log.info(s"Processing complete with results [$result]")
case unknown =>
log.error(s"Unknown message [$unknown]")
}
val emptyResult = Seq[(TaskId, Boolean)]()
class TasksProcessorResultActor extends Actor {
context.setReceiveTimeout(3 seconds)
override def receive = default(0, emptyResult)
def default(awaitResults: Int, completedTasks: Seq[(TaskId, Boolean)]): Receive = {
case tasks: Seq[TaskId] =>
tasks.foreach {
case task =>
context.actorOf(Props(new TaskProcessorActor(task)))
}
context become default(tasks.size, emptyResult)
case TaskComplete(id, isSuccess) =>
val results = completedTasks :+ ((id, isSuccess))
if (results.size == awaitResults) {
context.parent ! ProcessingComplete(results)
} else {
context become default(awaitResults, results)
}
case unknown =>
log.error(s"Unknown message [$unknown]")
}
}
class TaskProcessorActor(taskId: TaskId) extends Actor with ActorLogging {
override def receive = default
dao ! DaoActor.LoadTaskDetails(taskId, self)
def default: Receive = {
case taskDetails: TaskDetails =>
taskDetails.status match {
case "success" =>
dao ! DaoActor.DeleteTask(taskDetails.id, self)
context.become(waitDaoResponse)
case _ =>
//TODO: do some processing
dao ! DaoActor.MarkSuccess(taskDetails.id, self)
context.become(waitDaoResponse)
}
case unknown =>
log.error(s"Unknown message [$unknown]")
}
def waitDaoResponse: Receive = {
case true =>
context.parent ! TaskComplete(taskId, true)
case false =>
context.parent ! TaskComplete(taskId, false)
}
}
}
Looks pretty good except you don't need to pass the receiver: ActorRef in your DaoActorMessges. The DaoActor includes a sender method as part of the actor trait. You can use this to send a message back to the sender. This reduces a lot of the clutter of your code, for example:
case DeleteTask(id) =>
sender ! true
Related
I've been trying to use websocket on play framework 2.5 in scala.
Now, I want to exchange multiple message types.
But, I get the following errors when a client sends a json via websocket.
Do I have to choose different way to exchange multiple message types?
missing something or is there any easier way?
[error] a.a.OneForOneStrategy - {"sort":"post", "to":"receiver","message":"Test"} (of class play.api.libs.json.JsObject)
scala.MatchError: {"sort":"post", "to":"receiver","message":"Test"} (of class play.api.libs.json.JsObject)
at controllers.MessageController$MessageActor$$anonfun$receive$1.applyOrElse(MessageController.scala:72)
at akka.actor.Actor$class.aroundReceive(Actor.scala:514)
at controllers.MessageController$MessageActor.aroundReceive(MessageController.scala:63)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:527)
at akka.actor.ActorCell.invoke(ActorCell.scala:496)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
at akka.dispatch.Mailbox.run(Mailbox.scala:224)
at akka.dispatch.Mailbox.exec(Mailbox.scala:234)
at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
models.scala
abstract class WSMessageIn()
// Classes for input
case class PostMessage(
sort: String = "post",
to: String,
...
message: Option[String] = None
) extends WSMessageIn
object PostMessage {
implicit val postMessageFormat = Json.format[PostMessage]
}
case class MessageFromIn(
sort: String = "receive",
from: String,
...
message: Option[String] = None
) extends WSMessageIn
object MessageFromIn {
implicit val messageFromInFormat = Json.format[MessageFromIn]
}
object WSMessageIn {
implicit def json2object(value: JsValue): WSMessageIn = {
(value \ "sort").as[String] match {
case "post" => value.as[PostMessage]
case "receive" => value.as[MessageFromIn]
}
}
implicit def object2json(in: WSMessageIn): JsValue = {
in match {
case in: PostMessage => Json.toJson(in)
case in: MessageFromIn => Json.toJson(in)
}
}
}
MessageController.scala
class MessageController #Inject() (
silhouette: Silhouette[DefaultEnv],
..
implicit val system: ActorSystem,
implicit val materializer: Materializer)
extends Controller {
class MessageActor(out: ActorRef) extends Actor {
override def receive = {
case message: JsValue =>
message match { // !!The error occurs here
case message: PostMessage =>
...
case message: MessageFromIn =>
...
}
}
}
object MessageActor {
def props(out: ActorRef) = Props(new MessageActor(out))
}
implicit val messageFlowTransformer = MessageFlowTransformer.jsonMessageFlowTransformer[JsValue, JsValue]
def socket() = WebSocket.acceptOrResult[JsValue, JsValue] { request =>
implicit val req = Request(request, AnyContentAsEmpty)
silhouette.SecuredRequestHandler { securedRequest =>
Future.successful(HandlerResult(Ok, Some(securedRequest.identity)))
}.map {
case HandlerResult(r, Some(user)) => Right(ActorFlow.actorRef(out => MessageActor.props(out)))
case HandlerResult(r, None) => Left(r)
}
}
}
As is: WebSocket.acceptOrResult[JsValue, JsValue] you expect your websocket to receive JsValue.
This seems to match you code below in MessageActor:
override def receive = {
case message: JsValue => // ...
}
However, you shouldn't expect the conversion from JsValue to WSMessageIn to happen without your intervention.
What you could do is simply:
override def receive = {
case message: JsValue =>
WSMessageIn.json2object(message) match {
case message: PostMessage =>
...
case message: MessageFromIn =>
...
}
}
You were probably expecting an implicitly conversion, you can get it by giving a hint to the compiler (i.e., saying what you expect):
override def receive = {
case message: JsValue =>
(message: WSMessageIn) match {
case message: PostMessage =>
...
case message: MessageFromIn =>
...
}
}
Note: you could turn abstract class WSMessageIn() into: sealed trait WSMessageIn
I have a play(2.4.2 which has akka 2.4.18) application in which I am using akka actors for uploading a file. I have a parent supervisor Actor with this kind of hierarchy
UploadSupervisor ---child---> UploadActor ---child--->
DataWriteActor & MetaWriteActor
The leaf actors MetaWriteActor & DataWriteActor does the actual writing. A very simplified version of my code is below:
First I have a actor supervisor:
class UploadSupervisor extends Actor {
val uploadActor = context.actorOf(Props(new UploadActor), "UploadActor")
override def supervisorStrategy = OneForOneStrategy() {
case _: Throwable => Restart
}
override def receive: Receive = {
case data: Data => uploadActor ! data
case meta: MetaInfo => uploadActor ! meta
//How do I send response outside of actor system?
case dataSuccess: DataUploadResponse => ??? //Line 10
case metaSuccess: MetaUploadResponse => ??? //Line 11
}
object UploadSupervisor {
val uploadSupervisor = Akka.system
.actorOf(Props(new UploadSupervisor), "UploadSupervisor")
}
//Request & Response case classes
case class Data(content: String)
case class MetaInfo(id: String, createdDate: Timestamp)
case class DataUploadResponse(location: String)
case class MetaUploadResponse(location: String)
UploadActor:-
class UploadActor extends Actor {
val dataWriteActor = context.actorOf(Props(new DataWriteActor), "dataWriteActor")
val metaWriteActor = context.actorOf(Props(new MetaWriteActor), "UploadActor")
override def receive = {
case data: Data => dataWriteActor ! data
case meta: MetaInfo => metaWriteActor ! meta
case dataResp: DataUploadResponse => context.parent ! dataResp
case metaResp: MetaUploadResponse => context.parent ! metaResp
}
}
DataWriteActor :
class DataWriteActor extends Actor {
case data: Data => //Do the writing
println("data write completed")
sender() ! DataUploadResponse("someLocation")
}
MetaWriteActor
class MetaWriteActor extends Actor {
case meta: MetaInfo=> //Do the writing
println(" meta info writing completed")
sender() ! MetaUploadResponse("someOtherLocation")
}
Somewhere outside Actor system:-
implicit val timeout = Timeout(10 seconds)
val f1 = UploadSupervisor.uploadSupervisor ? Data("Hello Akka").mapTo(implicitly[scala.reflect.ClassTag[DataUploadResponse]])
val f2 = UploadSupervisor.uploadSupervisor ? MetaInfo("1234", new Timestamp(new Date().getTime).mapTo(implicitly[scala.reflect.ClassTag[MetaUploadResponse]])
//Do something with futures
The question is how to send the response outside the actor system? Because in Line 10 & 11, I can't use sender ! msg because the current sender is the UploadActor.
You could keep in UploadSupervisor references to the initial senders:
class UploadSupervisor extends Actor {
val uploadActor = context.actorOf(Props[UploadActor], "UploadActor")
override val supervisorStrategy = OneForOneStrategy() {
case _ => Restart
}
var dataSender: Option[ActorRef] = None
var metaSender: Option[ActorRef] = None
def receive = {
case data: Data =>
val s = sender
dataSender = Option(s)
uploadActor ! data
case meta: MetaInfo =>
val s = sender
metaSender = Option(s)
uploadActor ! meta
case dataSuccess: DataUploadResponse =>
dataSender.foreach(_ ! dataSuccess)
case metaSuccess: MetaUploadResponse =>
metaSender.foreach(_ ! metaSuccess)
}
}
To send messages to UploadSupervisor:
implicit val timeout = Timeout(10 seconds)
val f1 = (UploadSupervisor.uploadSupervisor ? Data("Hello Akka")).mapTo[DataUploadResponse]
val f2 = (UploadSupervisor.uploadSupervisor ? MetaInfo("1234", new Timestamp(new Date().getTime)).mapTo[MetaUploadResponse]
The above assumes that you're sending one Data message and one MetaInfo message to UploadSupervisor at a time. This approach will break down if you send multiple Data and MetaInfo messages and expect concurrent replies. A more general solution is to include the reference to the initial sender in additional case classes that wrap your existing case classes, passing this reference through your actor hierarchy:
case class DataMsg(data: Data, target: ActorRef)
case class MetaInfoMsg(metaInfo: MetaInfo, target: ActorRef)
case class DataUploadMsg(response: DataUploadResponse, target: ActorRef)
case class MetaUploadMsg(response: MetaUploadResponse, target: ActorRef)
class UploadSupervisor extends Actor {
val uploadActor = context.actorOf(Props[UploadActor], "UploadActor")
override val supervisorStrategy = OneForOneStrategy() {
case _ => Restart
}
def receive = {
case data: Data =>
val s = sender
uploadActor ! DataMsg(data, s)
case meta: MetaInfo =>
val s = sender
uploadActor ! MetaInfoMsg(meta, s)
case DataUploadMsg(response, target) =>
target ! response
case MetaUploadMsg(response, target) =>
target ! response
}
}
The UploadActor:
class UploadActor extends Actor {
val dataWriteActor = context.actorOf(Props[DataWriteActor], "dataWriteActor")
val metaWriteActor = context.actorOf(Props[MetaWriteActor], "UploadActor")
def receive = {
case data: DataMsg => dataWriteActor ! data
case meta: MetaInfoMsg => metaWriteActor ! meta
case dataResp: DataUploadMsg => context.parent ! dataResp
case metaResp: MetaUploadMsg => context.parent ! metaResp
}
}
The writers:
class DataWriteActor extends Actor {
def receive = {
case DataMsg(data, target) =>
// do the writing
println("data write completed")
sender ! DataUploadMsg(DataUploadResponse("someLocation"), target)
}
}
class MetaWriteActor extends Actor {
def receive = {
case MetaInfoMsg(meta, target) =>
// do the writing
println("meta info writing completed")
sender ! MetaUploadMsg(MetaUploadResponse("someOtherLocation"), target)
}
}
What I want to do is basically 1:1 this: Scala Play Websocket - use one out actor to send both: Array[Byte] and String messages
Sadly the API has changed a lot since 2.4 (I am on 2.6.0-M4 right now).
What I tried (does not compile for obvious reasons):
WebSocket.accept[WSMessage, WSMessage]
{
request =>
ActorFlow.actorRef
{
out => WebSocketActor.props(out)
}
}
sealed trait WSMessage
case class StringMessage(s: String) extends WSMessage
case class BinaryMessage(a: Array[Byte]) extends WSMessage
case class JsonMessage(js: JsValue) extends WSMessage
object MyMessageFlowTransformer
{
implicit val WSMessageFlowTransformer: MessageFlowTransformer[WSMessage, WSMessage] =
{
new MessageFlowTransformer[WSMessage, WSMessage]
{
def transform(flow: Flow[WSMessage, WSMessage, _]) =
{
AkkaStreams.bypassWith[Message, WSMessage, Message](Flow[Message] collect
{
case StringMessage(s) => Left(s)
case BinaryMessage(b) => Left(b)
case JsonMessage(j) => Left(j)
case _ => Right(CloseMessage(Some(CloseCodes.Unacceptable)))
})(flow map WSMessage.apply)
}
}
}
}
I am somewhat lost. play.api.http.websocket.Message is a sealed trait, and probably for a good reason...
Define a MessageFlowTransformer[Either[String, Array[Byte]], Either[String, Array[Byte]]]:
type WSMessage = Either[String, Array[Byte]]
implicit val mixedMessageFlowTransformer: MessageFlowTransformer[WSMessage, WSMessage] = {
new MessageFlowTransformer[WSMessage, WSMessage] {
def transform(flow: Flow[WSMessage, WSMessage, _]) = {
AkkaStreams.bypassWith[Message, WSMessage, Message](Flow[Message].collect {
case BinaryMessage(data) => Left(Right(data.toArray))
case TextMessage(text) => Left(Left(text))
})(flow map {
case Right(data) => BinaryMessage.apply(ByteString.apply(data))
case Left(text) => TextMessage.apply(text)
})
}
}
}
I have Play websockets action:
def socket = WebSocket.acceptWithActor[String, Array[Byte]] { request => out =>
Props(new WebSocketInActor(out))
}
Generally I need to send to browser large raw arrays of data. But sometimes I need to send some small string data. In browser I can detect is data in text format or raw ArrayBuffer.
If I create actor that sends String, I can send string messages, If I create actor that sends with Array[Byte], I can send raw arrays. Both situations I don't need to change client code. So, how can I force Play to use both sending methods with one out actor?
Ah, those answers that comes just after you post question on SO. Looking through reference and sourcecode, I found that there is mixedFrame FrameFromatter: https://github.com/playframework/playframework/blob/2.4.x/framework/src/play/src/main/scala/play/api/mvc/WebSocket.scala#L75
So you just need to say that you will respond with Either[String, Array[Byte]] and if you want to send string use Left(somestring) or else use Right[somearray].
class WebSocketInActor(out: ActorRef) extends Actor {
override def preStart() = {
println("User connected")
val s = "Hello"
out ! Left(s)
out ! Right(s.getBytes("utf8"))
}
override def postStop() = {
println("User discconnected")
}
def receive = {
case msg: String => {
}
case _ =>
}
}
def socket = WebSocket.acceptWithActor[String, Either[String, Array[Byte]]] { request => out =>
Props(new WebSocketInActor(out))
}
UPDATE:
Or you can go one step further and create your own frame formatter
sealed trait WSMessage
case class StringMessage(s: String) extends WSMessage
case class BinaryMessage(a: Array[Byte]) extends WSMessage
case class JsonMessage(js: JsValue) extends WSMessage
implicit object myFrameFormatter extends BasicFrameFormatter[WSMessage] {
private val textFrameClass = classOf[TextFrame]
private val binaryFrameClass = classOf[BinaryFrame]
def toFrame(message: WSMessage): BasicFrame = message match {
case StringMessage(s) => TextFrame(s)
case BinaryMessage(a) => BinaryFrame(a)
case JsonMessage(js) => TextFrame(Json.stringify(js))
}
def fromFrame(frame: BasicFrame): WSMessage = frame match {
case TextFrame(s) => StringMessage(s)
case BinaryFrame(a) => BinaryMessage(a)
}
def fromFrameDefined(clazz: Class[_]): Boolean = clazz match {
case `textFrameClass` => true
case `binaryFrameClass` => true
case _ => false // shouldn't be reachable
}
}
class WebSocketInActor(out: ActorRef) extends Actor {
override def preStart() = {
println("User connected")
val s = "Hello"
val a:Array[Byte] = Array(100, 50, 30).map(_.toByte)
out ! StringMessage(s)
out ! JsonMessage(Json.obj("txt" -> s, "array" -> a))
out ! BinaryMessage(a)
}
override def postStop() = {
println("User discconnected")
}
def receive = {
case msg: String => {
}
case _ =>
}
}
def socket = WebSocket.acceptWithActor[String, WSMessage] { request => out =>
Props(new WebSocketInActor(out))
}
I want to be able to make concurrent requests to multiple data repositories and consolidate the results. I am trying to understand if my approach is at all valid or if there is a better way to approach this problem. I am definitely new to Akka / Spray / Scala and really want to get a better understanding of how to properly build these components. Any suggestions / Tips would be greatly appreciated. Trying to wrap my head around the use of actors and futures for this type of implementation.
Spray Service:
trait DemoService extends HttpService with Actor with ActorLogging {
implicit val timeout = Timeout(5 seconds) // needed for `?` below
val mongoMasterActor = context.actorOf(Props[MongoMasterActor], "redisactor")
val dbMaster = context.actorOf(Props[DbMasterActor], "dbactor")
val messageApiRouting =
path("summary" / Segment / Segment) { (dataset, timeslice) =>
onComplete(getDbResponses(dbMaster, dataset, timeslice)) {
case Success(dbMessageResponse) => complete(s"The result was $dbMessageResponse")
case Failure(ex) => complete(s"An error occurred: ${ex.getMessage}")
}
}
/** Passes the desired actor reference for a specific dataset and timeslice for summary data retrieval
*
* #param mongoActor an actor reference to the RedisActor that will handle the appropriate request routing
* #param dataset The dataset for which the summary has been requested
* #param timeslice The timeslice (Month, Week, Day, etc.) for which the summary has been requested
*/
def getSummary(mongoActor: ActorRef, dataset: String, timeslice: String): Future[DbMessageResponse] = {
log.debug(s"dataset: $dataset timeslice: $timeslice")
val dbMessage = DbMessage("summary", dataset + timeslice)
(mongoActor ? dbMessage).mapTo[DbMessageResponse]
}
def getDbResponses(dbActor: ActorRef, dataset: String, timeslice: String): Future[SummaryResponse] = {
log.debug(s"dataset: $dataset timeslice: $timeslice")
val dbMessage = DbMessage("summary", dataset + timeslice)
(dbActor ? dbMessage).mapTo[SummaryResponse]
}
def getSummaryPayload(mongoSummary: DbMessageResponse, redisSummary: DbMessageResponse): String = {
mongoSummary.response + redisSummary.response
}
}
Akka Actor / Future mock db requests:
class DbMasterActor extends Actor with ActorLogging {
private var originalSender: ActorRef = _
//TODO: Need to add routing to the config to limit instances
val summaryActor = context.actorOf(Props(new SummaryActor), "summaryactor")
def receive = {
case msg: DbMessage => {
this.originalSender = sender
msg.query match {
case "summary" => {
getDbResults().onComplete{
case Success(result) => originalSender ! result
case Failure(ex) => log.error(ex.getMessage)
}
}
}
}
//If not match log an error
case _ => log.error("Received unknown message: {} ")
}
def getDbResults(): Future[SummaryResponse] = {
log.debug("hitting db results")
val mongoResult = Future{ Thread.sleep(500); "Mongo"}
val redisResult = Future{ Thread.sleep(800); "redis"}
for{
mResult <- mongoResult
rResult <- redisResult
} yield SummaryResponse(mResult, rResult)
}
}
Following the reading of Effective Akka by Jamie Allen, I am going to attempt to apply his "Cameo" pattern suggestion.
Slideshare:
http://www.slideshare.net/shinolajla/effective-akka-scalaio
Github:
https://github.com/jamie-allen/effective_akka
I think what I created will work, but doesn't sound like the best approach based on Jamie's comments in his talks. I will update / edit back to this post what I have implemented (or try to).
Summary Actor (Cameo Actor):
object SummaryResponseHandler {
case object DbRetrievalTimeout
def props(mongoDb: ActorRef, redisDb: ActorRef, originalSender: ActorRef): Props = {
Props(new SummaryResponseHandler(mongoDb, redisDb, originalSender))
}
}
class SummaryResponseHandler(mongoDb: ActorRef, redisDb: ActorRef,
originalSender: ActorRef) extends Actor with ActorLogging {
import SummaryResponseHandler._
var mongoSummary, redisSummary: Option[String] = None
def receive = LoggingReceive {
case MongoSummary(summary) =>
log.debug(s"Received mongo summary: $summary")
mongoSummary = summary
collectSummaries
case RedisSummary(summary) =>
log.debug(s"Received redis summary: $summary")
redisSummary = summary
collectSummaries
case DbRetrievalTimeout =>
log.debug("Timeout occurred")
sendResponseAndShutdown(DbRetrievalTimeout)
}
def collectSummaries = (mongoSummary, redisSummary) match {
case (Some(m), Some(r)) =>
log.debug(s"Values received for both databases")
timeoutMessager.cancel
sendResponseAndShutdown(DataSetSummary(mongoSummary, redisSummary))
case _ =>
}
def sendResponseAndShutdown(response: Any) = {
originalSender ! response
log.debug("Stopping context capturing actor")
context.stop(self)
}
import context.dispatcher
val timeoutMessager = context.system.scheduler.scheduleOnce(
250 milliseconds, self, DbRetrievalTimeout)
}
class SummaryRetriever(mongoDb: ActorRef, redisDb: ActorRef) extends Actor with ActorLogging {
def receive = {
case GetSummary(dataSet) =>
log.debug("received dataSet")
val originalSender = sender
val handler = context.actorOf(SummaryResponseHandler.props(mongoDb,redisDb, originalSender), "cameo-message-handler")
mongoDb.tell(GetSummary(dataSet), handler)
redisDb.tell(GetSummary(dataSet), handler)
case _ => log.debug(s"Unknown result $GetSummary(datset)")
}
}
Common:
case class GetSummary(dataSet: String)
case class DataSetSummary(
mongo: Option[String],
redis: Option[String]
)
case class MongoSummary(
summary: Option[String]
)
case class RedisSummary(
summary: Option[String]
)
trait MongoProxy extends Actor with ActorLogging
trait RedisProxy extends Actor with ActorLogging
Mock Stubs:
class MongoProxyStub extends RedisProxy {
val summaryData = Map[String, String](
"dataset1" -> "MongoData1",
"dataset2" -> "MongoData2")
def receive = LoggingReceive {
case GetSummary(dataSet: String) =>
log.debug(s"Received GetSummary for ID: $dataSet")
summaryData.get(dataSet) match {
case Some(data) => sender ! MongoSummary(Some(data))
case None => sender ! MongoSummary(Some(""))
}
}
}
class RedisProxyStub extends MongoProxy{
val summaryData = Map[String, String](
"dataset1" -> "RedisData1",
"dataset2" -> "RedisData2")
def receive = LoggingReceive {
case GetSummary(dataSet: String) =>
log.debug(s"Received GetSummary for ID: $dataSet")
summaryData.get(dataSet) match {
case Some(data) => sender ! RedisSummary(Some(data))
case None => sender ! RedisSummary(Some(""))
}
}
}
Boot (You should use test, but was just wanting to run from boot):
object Boot extends App{
val system = ActorSystem("DbSystem")
val redisProxy = system.actorOf(Props[RedisProxyStub], "cameo-success-mongo")
val mongoProxy = system.actorOf(Props[MongoProxyStub], "cameo-success-redis")
val summaryRetrieverActor = system.actorOf(Props(new SummaryRetriever(redisProxy, mongoProxy)), "cameo-retriever1")
implicit val timeout = Timeout(5 seconds)
val future = summaryRetrieverActor ? GetSummary("dataset1")
val result = Await.result(future, timeout.duration).asInstanceOf[DataSetSummary]
println(Some(result.mongo).x)
println(result.redis)
system.shutdown()
}
Application Config:
akka.loglevel = "DEBUG"
akka.event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
akka.actor.debug.autoreceive = on
akka.actor.debug.lifecycle = on
akka.actor.debug.receive = on
akka.actor.debug.event-stream = on