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")
}