Apache Pekko

Pekko integration

Apache Pekko is a toolkit and runtime for building highly concurrent, distributed, and fault tolerant event-driven applications on the JVM. Scalatra allows you to easily mix it into your application.

Dependencies

The following dependencies will be needed to make the sample application work.

"org.apache.pekko" %% "pekko-actor" % "1.0.0",
"com.softwaremill.sttp.client3" %% "core" % "3.9.0",

Setting up your Scalatra app with Pekko

When you’re using Pekko, you’ll want to start your Actors and ActorSystem from inside the ScalatraBootstrap class. You can then pass those into the constructors of your servlets as necessary:

import org.apache.pekko.actor.{Props, ActorSystem}
import com.example.app._
import org.scalatra._
import jakarta.servlet.ServletContext


class ScalatraBootstrap extends LifeCycle {

  val system = ActorSystem()
  val myActor = system.actorOf(Props.apply[MyActor]())

  override def init(context: ServletContext) = {
    context.mount(new PageRetriever(system), "/*")
    context.mount(new MyActorApp(system, myActor), "/actors/*")
  }

  override def destroy(context:ServletContext) = {
    system.terminate()
  }
}

It’s also considered good form to shut the ActorSystem down when you’re done with it. Keep in mind that a servlet context destroy does not necessarily mean a full application shutdown, it might be a reload - so you’ll need to release the ActorSystem resources when your Scalatra application is destroyed.

Using Scala Futures

Scalatra’s FutureSupport trait provides a mechanism for adding Futures to your routes. At the point where you

The generic case looks like this (but it won’t compile):

import org.apache.pekko.dispatch._
import org.scalatra.FutureSupport

class MyAppServlet extends ScalatraServlet with FutureSupport {
  get("/"){
    new AsyncResult { val is =
      Future {
        // Add async logic here
        <html><body>Hello Pekko</body></html>
      }
    }
  }
}

Async request example

See pekko-examples for a minimal and standalone project containing the examples in this guide.

As a more concrete example, here’s how you’d make an asynchronous HTTP request from inside one of your actions, using the Dispatch http client and an Pekko ActorSystem.

package com.example.app

import org.apache.pekko.actor.ActorSystem
import sttp.client3._

import org.scalatra._

import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.{Failure, Success, Try}

class FutureController(system: ActorSystem) extends ScalatraServlet with FutureSupport {

  protected implicit def executor: ExecutionContext = system.dispatcher

  get("/") {
    new AsyncResult { val is =
      HttpClient.retrievePage()
    }
  }

}

object HttpClient {

  def retrievePage()(implicit ctx: ExecutionContext): Future[String] = {
    Future {
      val backend = HttpClientSyncBackend()
      val request = basicRequest.get(uri"https://scalatra.org/").response(asStringAlways)
      val response = request.send(backend)
      response.body
    }
  }
}

AsyncResult isn’t strictly necessary. It’s a way to ensure that if you close your Future over mutable state (such as a request object or a var) that the state is captured at the point you hand off to the Future.

If you attempt to use mutable state inside your Future without AsyncResult (e.g. calling request.headers or something), you’ll get an exception. If you use AsyncResult, it’ll work. So, you’re trading a bit of boilerplate code for a bit of safety. If you can remember not to close over mutable state, don’t bother with AsyncResult.

Actor example

See pekko-examples for a minimal and standalone project containing the examples in this guide.

When you use Scalatra with Pekko, you most likely want to return a result of some sort. So you’re probably going to send a message to an Actor which will reply to you. The method you use for that returns a Future. Typically, this involves Pekko’s ask pattern.

When the request you get just needs to trigger something on an Actor using the fire-and-forget tell pattern, then you don’t need a Future. In this case, you probably you want to reply with the Accepted status or something like it.

Here’s some example code:

package com.example.app

import org.apache.pekko.actor.{Actor, ActorRef, ActorSystem}
import org.apache.pekko.pattern.ask
import org.apache.pekko.util.Timeout
import org.scalatra.{Accepted, FutureSupport, ScalatraServlet}

import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

class MyActorApp(system:ActorSystem, myActor:ActorRef) extends ScalatraServlet with FutureSupport {

  implicit val timeout = new Timeout(2 seconds)
  protected implicit def executor: ExecutionContext = system.dispatcher

  // You'll see the output from this in the browser.
  get("/ask") {
    myActor ? "Do stuff and give me an answer"
  }

  // You'll see the output from this in your terminal.
  get("/tell") {
    myActor ! "Hey, you know what?"
    Accepted()
  }

}

class MyActor extends Actor {
  def receive = {
    case "Do stuff and give me an answer" => sender() ! "The answer is 42"
    case "Hey, you know what?" => println("Yeah I know... oh boy do I know")
  }

}

Once again, if we wanted to ensure that it was safe to close over mutable state, we could have used AsyncResult with out Actors.