Search code examples
scalaserializationakkaslickakka-remote-actor

akka serialization exception sending slick table query across remote actors


Am trying to send the table query object from local actor to remote actor, but akka gives serialization exception

Remote Actor looks like this and am using active slick pattern here

object RemoteActor {
  case class Entry[M, I: BaseColumnType, T <: IdTable[M, I]](tableWithIdQuery: TableWithIdQuery[M, I, T], model: M)
}

class RemoteActor extends Actor with ActorLogging {

import RemoteActor._

lazy val db = Database.forURL(
  url = "jdbc:mysql://localhost/demo",
  driver = "com.mysql.jdbc.Driver",
  user= "root",
  password= "root")

override def receive = {
  case Entry(table, model) =>
      //I will wrap this code in a Future and use akka.pattern.pipe
      // thus avoiding deafening the actor
      //avoided wrapping in future just to avoid clutter
      db.withSession { implicit sx =>
      table.createIfNotExists
      table.save(model)
    }
    log.info("done saving to database")
  case msg => log.info(s"unknown message of type ${msg.getClass}")
}
}

Local Actor looks like this. Local Actor builds TableWithIdQuery and passes it to the Remote Actor mentioned above.

class LocalActor extends Actor with ActorLogging {

import LocalActor._

var remoteActor: Option[ActorSelection] = None

override def preStart(): Unit = {
  remoteActor =   Some(context.actorSelection("akka.tcp://ActorSystem@127.0.0.1:2222/user/RemoteActor"))
  remoteActor.getOrElse {
    println(" unreachable, shutting down :(")
    context.stop(self)
  }
}

override def receive = {
  case Send =>
    case class User(name: String, id: Option[Long] = None)

    class Users(tag: Tag) extends IdTable[User, Long](tag, "users") {
      def name = column[String]("name")
      def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
      def * = (name, id.?) <> (User.tupled, User.unapply)
    }

    val users = new TableWithIdQuery[User, Long, Users](tag => new Users(tag)) {
      override def extractId(model: User): Option[Long] = model.id
      override def withId(model: User, id: Long): User = model.copy(id = Some(id))
    }

    import remote.RemoteActor._

    remoteActor.map(remote => remote ! Entry[User, Long, Users](users,   User("pamu nagarjuna")))
    log.info("message sent :)")

    case msg => log.info(s"unknown message of type ${msg.getClass}")
}
}

Solution

  • It's not safe to assume that any thing like a Query is serializable. You really ought to run the query in a local context, map the results to a set of serializable types (e.g. immutable collections, case classes), and send that.