1

I'm working with Spray+Akka (I want to say "Framework" but they want to be called as a "Library"). I get a REST call and I return a response. However, I find my processing a bit repetitive. Here are the two blocks when I received POST request from two APIs:

post {
                entity(as[JObject]) { company =>
                  if (AuthInfo.rejectedOrNot) {
                    val response = (secCompanyActor ? jObjectFromWeb(company))
                      .mapTo[TransOk]
                      .map(result => result.succeedOrNot match {
                      case true => (OK, "transaction successful")
                      case false => (BadRequest, result.errorMessage)
                    }).recover { case _ => (BadRequest, "An error has occurred! We will fix this")}
                    complete(response)

                  } else {
                    complete(Unauthorized, "Please use HTTP header to authorize this command.")
                  }
                }

The other block is strikingly similar, but the only difference is instead of sending jObjectFromWeb(...), I have to send this message: jObjectFromComputer(...).

I wanted to create a function where I pass in the message and then send the message from that function, but Akka's messages aren't TYPED! I understand the philosophy from untyped message and I'm not arguing for anything, but I want a nice solution. Yes, one solution is that I pass in a string like sendMessageToActor(actor, "jObjectFromWeb") and in the actual function, I use a pattern match to send the corresponding message.

But it is a bit ugly and not very modular (what if I have 10 messages to send?).

Is there any way to do this? I'm not quite familar with Java's reflection, but it would be very nice if I can just find the message case class/object through a string.


This is my Akka actor side of implementation:

class SECCompanyActor extends Actor with ActorLogging {
  import com.mturk.tasks.SECcompany.SECCompanyProtocol._

  def receive = {

    case jObjectFromCasper(jObject) =>
      //Do some logic
      sender ! TransOk(result._1, result._2, result._3)

    case jObjectFromWeb(jObject) =>
      // Do some other logic
      sender ! TransOk(result._1, result._2, result._3)
  }

Here is my Akka message:

object SECCompanyProtocol {
  case class jObjectFromCasper(jObject: JObject)
  case class TransOk(company: Option[Company.Company], succeedOrNot: Boolean, errorMessage: Option[String])
  case class jObjectFromWeb(jObject: JObject)
}
Mario Camou
  • 2,303
  • 16
  • 28
windweller
  • 2,365
  • 5
  • 33
  • 56

1 Answers1

1

How about this:

def doPost(messageConstructor: Company => AnyRef): Route = {
  entity(as[JObject]) { company =>
    if (AuthInfo.rejectedOrNot) {
      val response = (secCompanyActor ? messageConstructor(company))
        .mapTo[TransOk]
        .map(result => result.succeedOrNot match {
          case true => (OK, "transaction successful")
          case false => (BadRequest, result.errorMessage)
        }).recover { case _ => (BadRequest, "An error has occurred! We will fix this")}
      complete(response)
    } else {
      complete(Unauthorized, "Please use HTTP header to authorize this command.")
    }
  }
}    

post {
  doPost(jObjectFromWeb.apply)
}

As stated by your comment, this works when jObjectFromWeb / jObjectFromComputer are case classes. jObjectFromWeb.apply corresponds to the apply method of the case class' companion object, which is automatically created for case classes. A more typesafe design would inherit both case classes from a single trait:

trait GenericJObject {
  val company: Company
}

case class JObjectFromWeb(override val company: Company) extends GenericJObject

case class JObjectFromComputer(override val company: Company) extends GenericJObject

def doPost(messageConstructor: Company => GenericJObject): Route = {
...
}

Also, it would be better for maintainability and for understanding your code to respect the Scala coding convention where class names start with an uppercase letter, otherwise someone might get confused and assume that jObjectFromXXX is a method and not a case class.

Mario Camou
  • 2,303
  • 16
  • 28
  • Well, although `Akka.ask` can be used with methods, `jObjectFromWeb ` is not a method, but a `message`. It's a case class. However, you proposal might be more interesting, but I want to use Akka Actor right now instead of Akka Future. (There are arguments made suggesting `Future` is better than `Actor`) – windweller Jul 07 '14 at 12:45
  • Just edited it, as I said in the edited version, I got confused because normally in Scala methods start with lowercase and classes start with uppercase. – Mario Camou Jul 07 '14 at 13:28
  • YES! It's my fault. I need to change that. Thank you! – windweller Jul 07 '14 at 13:39
  • Sorry! One little thing: I start to get confused because I'm unfamiliar with the construction you used (I do understand `apply`). In the first `doPost()`, am I still sending `prepareMessage`? – windweller Jul 07 '14 at 13:42
  • I just corrected a typo: the call to `ask` should receive `messageConstructor(company)` instead of `prepareMessage(company)`. In short, in Scala you can use any object as if it was a function by defining an `apply` method on it. So, when you call `JObjectFromWeb(company)` you are actually calling `JObjectFromWeb.apply(company)`. With case classes, the Scala compiler automatically defines an `apply` method on its companion object that calls the class constructor with the same parameters. – Mario Camou Jul 07 '14 at 16:40
  • For more details on `apply`, have a look at the answer to this question: http://stackoverflow.com/questions/9737352/what-is-the-apply-function-in-scala – Mario Camou Jul 07 '14 at 16:40
  • StackOverflow discourages long discussion and I've already taken too much of your time so I accepted and upvoted your answer! One little thing: I'm not putting `company` into the message -> I'm putting `jObject` into the message. You already explained the theory so I'll do the conversion work. Thank you! – windweller Jul 07 '14 at 17:37