关于设计的这个问题,例如现在我有一些加载任务列表并尝试处理每个任务的过程,它看起来像这样:
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
}
}
我的问题:我需要如何更改此代码才能获得真实的'基于演员的应用程序,不使用期货或询问模式。
PS:我读过我只需要使用演员告诉(!)但我不能理解如何重写我的逻辑和DAO作为演员。PSS:我自己的方法
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)
}
}
}
答案 0 :(得分:0)
看起来很不错,除非您不需要传递receiver: ActorRef
中的DaoActorMessges
。 DaoActor
包含发件人方法,作为actor trait的一部分。您可以使用它将消息发送回发件人。这减少了代码的大量混乱,例如:
case DeleteTask(id) =>
sender ! true