告诉不要问并使用演员告诉未来

时间:2015-03-12 18:56:58

标签: scala akka

关于设计的这个问题,例如现在我有一些加载任务列表并尝试处理每个任务的过程,它看起来像这样:

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)
    }
  }

}

1 个答案:

答案 0 :(得分:0)

看起来很不错,除非您不需要传递receiver: ActorRef中的DaoActorMessgesDaoActor包含发件人方法,作为actor trait的一部分。您可以使用它将消息发送回发件人。这减少了代码的大量混乱,例如:

case DeleteTask(id) =>
  sender ! true