Home > Back-end >  Need help understanding return type for Akka Typed ask
Need help understanding return type for Akka Typed ask

Time:04-03

I am trying to get "ask" working for Akka Typed. I have followed examples online, and I thought I pretty much replicated what they showed, but I'm getting an compiler error when I try to evaluate the response from the "ask". Here's my minimal reproducible example.

SuperSimpleAsker is an actor that is requesting a "widget" from the MyWidgetKeeper actor. The response is a string representing the widget's id. All I'm trying to do so far is log the received widget id as a "Success" message, and will add more stuff to do with the id later. When the SuperSimpleAsker is created, the ActorRef of the MyWidgetKeeper is passed in. I have left out the Main program that creates the actors to keep the code simple.

The error that I get is:

type mismatch;
 found   : Unit
 required: widgets.SuperSimpleAsker.Request

This error occurs on both of the logger.* lines (inside of the Case Failure and Case Success blocks toward the end of the code listing).

I don't understand what part of the code is requiring a "widgets.SuperSimpleAsker.Request" object or why.

package widgets

import scala.concurrent.duration.DurationInt
import scala.util.{Failure, Success}
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.{ActorRef, Behavior}
import akka.util.Timeout
import com.typesafe.scalalogging.LazyLogging


object MyWidgetKeeper {
  sealed trait Request
  case class GetWidget(replyTo: ActorRef[Response]) extends Request

  sealed trait Response
  case class WidgetResponse(widget: String) extends Response

  def apply(): Behavior[Request] =
    new MyWidgetKeeper().myWidgetKeeper()

}

class MyWidgetKeeper {
  import MyWidgetKeeper._
  def myWidgetKeeper(): Behavior[Request] = {
    Behaviors.receive { (context, message) =>
      message match {
        case GetWidget(replyTo) =>
          replyTo ! WidgetResponse("12345")
          Behaviors.same
      }
    }
  }
}

object SuperSimpleAsker {
  sealed trait Request
  case object DoStuff extends Request

  def apply(widgetKeeper: ActorRef[MyWidgetKeeper.Request]): Behavior[Request] =
    new SuperSimpleAsker(widgetKeeper).simpleAsker()
}

class SuperSimpleAsker(widgetKeeper: ActorRef[MyWidgetKeeper.Request]) extends LazyLogging{
  import SuperSimpleAsker._
  import widgets.MyWidgetKeeper.GetWidget
  
  private def simpleAsker(): Behavior[Request] = {
    Behaviors.receive { (context, message) =>
      message match {
        case DoStuff =>
          logger.info(f"Doing stuff")
          implicit val timeout = Timeout(2000 millis)
          context.ask(widgetKeeper, GetWidget)
          {
            case Failure(exception) =>
              logger.error(f"Failed: ${exception.getMessage}")
            case Success(response: MyWidgetKeeper.Response) =>
              response match {
                case MyWidgetKeeper.WidgetResponse(id) =>
                  logger.info(f"Success: Got Widget# $id")
                  // Do some more stuff with the widget id
              }
          }
          Behaviors.same
      }
    }
  }
}

CodePudding user response:

In Akka Typed's context.ask, the passed function converts the successful or failed ask into a message which gets sent to the actor, ideally without performing a side effect.

So your SuperSimpleAsker will have to add messages that the ask can be converted to:

object SuperSimpleAsker {
  sealed trait Request
  case object DoStuff extends Request

  case class WidgetResponseFor(widgetId: String) extends Request
  case object NoWidgetResponse extends Request

  def apply(widgetKeeper: ActorRef[MyWidgetKeeper.Request]): Behavior[Request] =
    new SuperSimpleAsker(widgetKeeper).simpleAsker()
}

class SuperSimpleAsker(widgetKeeper: ActorRef[MyWidgetKeeper.Request]) extends LazyLogging{
  import SuperSimpleAsker._
  import widgets.MyWidgetKeeper.GetWidget
  
  private def simpleAsker(): Behavior[Request] = {
    Behaviors.receive { (context, message) =>
      message match {
        case DoStuff =>
          logger.info(f"Doing stuff")
          implicit val timeout = Timeout(2000 millis)
          context.ask(widgetKeeper, GetWidget)
          {
            case Failure(_) => // there's actually only one possible exception: timed out
              NoWidgetResponse
            case Success(response: MyWidgetKeeper.Response) =>
              WidgetResponseFor(response.widget)
          }
          Behaviors.same

        case WidgetResponseFor(id) =>
          logger.info(f"Success: Got Widget# $id")
          // Do stuff with the widget id

          Behaviors.same

        case NoWidgetResponse =>
          logger.error("Failed")
          Behaviors.same
      }
    }
  }
}
  • Related