Akka HTTP (formerly known as spray) is an Akka-based library for implementing HTTP services.

Akka HTTP is frequently used to write server-side (a.k.a back-end) REST APIs; for instance, at 47 Degrees, we have use it to write the back-end of the 9 Cards and Scala Days mobile applications. Akka HTTP makes it easy to write these types of APIs due to its routing DSL. In this DSL, routes are written using directives that filter incoming requests and end in a complete directive. The complete directive can take just a message, or just the status code to give back; but more frequently, it takes an expression, or program call, whose result is the response body that is given back to the client as an argument.

The goal of freestyle integration with Akka HTTP is to allow programmers to pass into a complete directive an expression of type fs : FreeS[F, A], where F[_] is the type constructor, and A is the type of the result. In respect to this goal, the Akka HTTP docs say that:

Transforming request […] bodies between over-the-wire formats and objects to be used in your application is done separately from the route declarations, in marshallers, which are pulled in implicitly using the “magnet” pattern. This means that you can complete a request with any kind of object as long as there is an implicit marshaller available in scope.

Thus, the freestyle integration for Akka HTTP provides an extension of that magnet pattern, which allows us to generate response marshallers. To be precise, what the integration gives us is a couple of implicit methods to generate an instance of ToEntityMarshaller[ FreeS[ F, A ] ] , or ToEntityMarshaller[ FreeS.Par[F, A]], where the parameter F[_] is the target of the algebra, A is the type of the base result. Note that the method has to be parameterized both on the F and on A, to make it as generic as possible.

import freestyle.free._
// import freestyle.free._

import freestyle.free.implicits._
// import freestyle.free.implicits._

import cats.{ ~>, Monad }
// import cats.{$tilde$greater, Monad}

import _root_.akka.http.scaladsl.marshalling.ToEntityMarshaller
// import _root_.akka.http.scaladsl.marshalling.ToEntityMarshaller

implicit def seqToEntityMarshaller[F[_], G[_], A](
    implicit NT: F ~> G,
    MonG: Monad[G],
    gem: ToEntityMarshaller[G[A]]): ToEntityMarshaller[FreeS[F, A]] =
  gem.compose((fs: FreeS[F, A]) => fs.interpret[G])
// seqToEntityMarshaller: [F[_], G[_], A](implicit NT: F ~> G, implicit MonG: cats.Monad[G], implicit gem: akka.http.scaladsl.marshalling.ToEntityMarshaller[G[A]])akka.http.scaladsl.marshalling.ToEntityMarshaller[freestyle.free.FreeS[F,A]]

implicit def parToEntityMarshaller[F[_], G[_], A](
    implicit NT: F ~> G,
    MonG: Monad[G],
    gem: ToEntityMarshaller[G[A]]): ToEntityMarshaller[FreeS.Par[F, A]] =
  gem.compose((fp: FreeS.Par[F, A]) => FreeS.liftPar(fp).interpret[G])
// parToEntityMarshaller: [F[_], G[_], A](implicit NT: F ~> G, implicit MonG: cats.Monad[G], implicit gem: akka.http.scaladsl.marshalling.ToEntityMarshaller[G[A]])akka.http.scaladsl.marshalling.ToEntityMarshaller[freestyle.free.FreeS.Par[F,A]]

To build an object marsh, our method needs to find in scope:

  • A base entity marshaller, base: ToEntityMarshaller[ G[A]], where G[_] is a constructor type, which may be different for each application; and A refers to the same type of the result in the response.
  • A way to interpret an expression of type FreeS[F, A] into a G[A]. This is built by the implicit methods in freestyle.free.implicits, from a natural transformation F ~> G and an instance of cats.Monad[G].

In essence, the generated marshaller first interprets the value of type FreeS[F,A] into a G[A], and then it passes the generated value to the base marshaller. To build an Akka HTTP route using a FreeS program, one only needs to bring this method into the implicit context, which can be done using an import freestyle.free.http.akka._ statement.

Small Example

As an example, here is a small API program mixing freestyle and Akka HTTP. The integration is not part of the freestyle main module. You need to import it separately, by adding the following dependency:

libraryDependencies += "io.frees" %% "frees-akka" % "0.8.2"

First, we (1) define a domain class User, (2) write a @free algebra that returns values of a type FreeS[F, User], and then (3) define an interpreter for that algebra to a type for which a Marshaller magnet exists. To keep things simple, we just interpret to Id:

import freestyle.free._
// import freestyle.free._

import freestyle.free.implicits._
// import freestyle.free.implicits._

case class User(name: String)
// defined class User

trait UserApp {
  def get(id: Int): FS[User]
// defined trait UserApp
// defined object UserApp

val app = UserApp.to[UserApp.Op]
// app: UserApp.To[UserApp.Op] = UserApp$To@60c134fa

To use this @free algebra in a route, we need (1) an EntityMarshaller for our domain object, and (2) an interpreter of the algebra to a suitable domain, which for this example will be Id:

import _root_.akka.http.scaladsl.marshalling.{ Marshaller, ToEntityMarshaller }
// import _root_.akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller}

import cats.Id
// import cats.Id

implicit val userMarshaller: ToEntityMarshaller[User] =
  Marshaller.StringMarshaller.compose((user: User) => s"User(${user.name})")
// userMarshaller: akka.http.scaladsl.marshalling.ToEntityMarshaller[User] = akka.http.scaladsl.marshalling.Marshaller$$anon$3@5f0937d4

implicit val handler: UserApp.Handler[Id] = new UserApp.Handler[Id] {
  private[this] val users: Map[Int, User] = Map( 1 -> User("foo") )
    override def get(id: Int): User = users.get(id).getOrElse(User("default"))
// handler: UserApp.Handler[cats.Id] = $anon$1@2ecb15fa

With these in scope, one can now write the route by using the marshaller generators from Akka HTTP:

import _root_.akka.http.scaladsl.server.Directives._
// import _root_.akka.http.scaladsl.server.Directives._

import _root_.akka.http.scaladsl.server.Route
// import _root_.akka.http.scaladsl.server.Route

import _root_.akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller}
// import _root_.akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller}

import _root_.akka.http.scaladsl.server.PathMatchers.IntNumber
// import _root_.akka.http.scaladsl.server.PathMatchers.IntNumber

val route: Route =  (get & path("user" / IntNumber)) { id =>
// route: akka.http.scaladsl.server.Route = akka.http.scaladsl.server.directives.BasicDirectives$$Lambda$9236/1694084276@52b8fc0f

More complex routes can be created as usual by chaining and grouping simple routes. Each route can use a different algebra, or can be interpreted to a different target type G[_].