emmea90 emmea90 - 8 days ago 6
Scala Question

Scala + Play Framework + Slick + Akka - DB Access from Akka Actor

At the receival of a TCP specific message, i need to query my DB. For that, i created an Actor that is called DBActor and it's loaded in

Application.scala
file

class Application @Inject() (system: ActorSystem) extends Controller {

val tcpServer = system.actorOf(Props[TCPServer], "tcpserver")
val dbActor = system.actorOf(Props[DBActor], "dbActor")
}


Tcp server is the actor that received the message and need to push it on DB Actor with that

val handler = context.actorSelection("/dbActor")


DB actor is so initialized in this way, according to Play Framework specifications

object DBActor {
def props() =
Props(classOf[DBActor])
}


class DBActor @Inject() (protected val dbConfigProvider:
DatabaseConfigProvider) extends Actor
with HasDatabaseConfigProvider[JdbcProfile]
with ActorLogging
with TableComponent {

import akka.io.Tcp._
import driver.api._

val table = TableQuery[Table]

def receive: Receive = {
case GetConfig(id) => {
sender ! Await.result(db.run(table.filter(_.id=== id).result.headOption),
Duration.Inf)
.map { x => x.config }
}
}
}


At the moment, actor is not constructed due to the error

Error injecting constructor, java.lang.IllegalArgumentException:
no matching constructor found on class tcp.DBActor for arguments []
at controllers.Application.<init>(Application.scala:17)
at controllers.Application.class(Application.scala:17)


So i need a way to inject the db configuration in the DBactor for querying the database or an alternative. I evaluated before that to inject a DAO or transforming the DAO I needed into an actor, both failed.

Question at this point is, does it make any sense giving an actor the access to the db or, at least, a controller? If can't be done, what are the alternatives?

Answer

What you need is an injected actor. The full description can be found here in the play documentation (https://www.playframework.com/documentation/2.5.x/ScalaAkka#Dependency-injecting-actors), but here's the gist of it:

You define the actor binding like so:

bindActor[DBActor]("db-actor")

And inject the actor in the controller like so:

class Application @Inject() (@Named("db-actor") dbActor: ActorRef) extends Controller {

On a different note, you should avoid Await.result whenever possible. In your scenario, this could probably easily be replaced with:

val senderRef = sender()
db.run(table.filter(_.id=== id).result.headOption)
  .map(res => senderRef ! res.config)

Note that the sender ref is stored before, because it is no longer valid inside the map (see scaladoc of the sender() method).

Comments