Debugging Akka Http and Slick

Hi folks,

I’m quite new to Scala and am working on an Akka Http REST API that connects to a Postgres database via Slick.

Basically, the routes should ask the categoryRepository actor to request a list of Categories from the CategoriesDao, which uses Slick to query the database.

CategoriesDao and categoryRepository should respond with a Seq[Category] which is then mapped to a List[Category] in the route, which should complete as JSON.

I figure somewhere along the way I am providing an incompatible value, or not handling a Future correctly but the compiler isn’t complaining. The code is compiling but then responding to GET requests with a 500 error.

Does anyone have advice on how I should go about debugging this?

The various parts are:

//CategoryRoutes.scala
import scala.concurrent._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
import akka.util.Timeout
import akka.pattern.ask
import com.foram.models.{Category, Categories}
import com.foram.actors.CategoryRepository._
import com.foram.Main.{categoryRepository}
import scala.concurrent.duration._

class CategoryRoutes {
  import com.foram.JsonFormats._
  implicit val timeout = Timeout(2 seconds)

  val categoryRoutes =
    pathPrefix("api" / "categories") {
      get {
          path(IntNumber) { id =>
            complete((categoryRepository ? GetCategoryByID(id)).mapTo[Option[Category]])
          } ~
          pathEndOrSingleSlash {
            complete((categoryRepository ? GetAllCategories).mapTo[List[Category]])
          }
      }
    }
}
// CategoryRepository.scala
import scala.concurrent.ExecutionContext.Implicits.global
import akka.actor.{Actor, ActorLogging, Props}
import com.foram.dao.CategoriesDao
import com.foram.models.{Category, Categories}
import scala.util.{Failure, Success}

object CategoryRepository {
  case class ActionPerformed(action: String)
  case object GetAllCategories
  case class GetCategoryByID(id: Int)
  case class CreateCategory(category: Category)
  case class UpdateCategory(id: Int, category: Category)
  case class DeleteCategory(id: Int)
  case object OperationSuccess
  def props = Props[CategoryRepository]
}

class CategoryRepository extends Actor with ActorLogging {
  import CategoryRepository._

  override def receive: Receive = {
    case GetAllCategories =>
      println(s"CategoryRepositoryActor Searching for categories")
      val allCategories = CategoriesDao.findAll
      allCategories.onComplete {
        case Success(categories) => sender() ! Categories(categories)
        case Failure(failure) => println("Data not found")
      }

    case GetCategoryByID(id) =>
      println(s"Finding category with id: $id")
      val category = CategoriesDao.findById(id)
      category.onComplete {
        case Success(category) => sender() ! category
        case Failure(failure) => println(s"$id category not found")
      }

    case CreateCategory(category) =>
      println(s"Creating category $category")
      CategoriesDao.create(category)
      sender() ! ActionPerformed(s"Category ${category.name} created.")

    // TODO
    //    case UpdateCategory(id, category) =>
    //      log.info(s"Updating category $category")
    //      categories = categories + (id -> category)
    //      sender() ! OperationSuccess

    case DeleteCategory(id) =>
      println(s"Removing category id $id")
      val category = CategoriesDao.delete(id)
      category.onComplete {
        case Success(category) => sender() ! ActionPerformed(s"Category $id deleted")
        case Failure(failure) => println(s"Unable to delete category $id")
      }
  }
}
// CategoriesDAO.scala
import com.foram.models.Category
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.Future

object CategoriesDao extends BaseDao {
  def findAll: Future[Seq[Category]] = db.run(categories.result)
  def findById(id: Int): Future[Category] = db.run(categories.filter(_.id === id).result.head)
  def create(category: Category) = categories.returning(categories.map(_.id)) += category
  def delete(id: Int) = db.run(categories.filter(_.id === id).delete)
}

I would be very grateful for any debugging advice anyone can offer.

The full source code is available in this GitHub repo.

Update

So it looks like the issue was that when responding from an actor with a future, you need to extract the sender before handling the future.

Something like:

case GetAllCategories =>
      println(s"Searching for categories")
      val allCategories = CategoriesDao.findAll
      val originalSender = sender
      allCategories.onComplete {
        case Success(categories) => originalSender ! categories.toList
        case Failure(failure) => println("Data not found")
      }

Yes, because sender() calls a method in the actor, calling it in an Future callback often means that you get the sender of a message which the actor is processing at some future time, which can lead to replying to the wrong sender.

1 Like