Bartosz Bąbol

Software engineering

Akka Http Rest Api

I. Intro

In this post I will try to explain simple rest api made in Akka http, Slick and PostgreSql. Here is git repo: Github repo

A) What is akka http?

Docs: Akka http docs

Zalando presentation: Zalando

Much more advanced akka http example which was great help for me, when doing my example: ArchDev example

B) What is slick?

Docs: Slick docs

Fantastic presentation by Stefan Zeiger: Stefan Zeiger presentation

II. Setup

A) Project structure

This is structure of sbt project. Check

1
build.sbt

to see what dependencies has been used in this project. Whole structure should look similar to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|-- akkaRestApi
    |-- build.sbt
    |-- project
    |-- src
    |   |-- main
    |   |   |-- java
    |   |   |-- resources
    |   |   |   |-- application.conf.example
    |   |   |   |-- db
    |   |   |   |   |-- migration
    |   |   |   |       |-- V1__Create_users_table.sql
    |   |   |   |       |-- V2__Create_posts_table.sql
    |   |   |   |       |-- V3__Create_comments_table.sql
    |   |   |   |-- public
    |   |   |       |-- index.html
    |   |   |-- scala
    |   |   |   |-- Main.scala
    |   |   |   |-- Routes.scala
    |   |   |   |-- api
    |   |   |   |   |-- ApiErrorHandler.scala
    |   |   |   |   |-- CommentsApi.scala
    |   |   |   |   |-- PostsApi.scala
    |   |   |   |   |-- UsersApi.scala
    |   |   |   |-- dao
    |   |   |   |   |-- BaseDao.scala
    |   |   |   |   |-- CommentsDao.scala
    |   |   |   |   |-- PostsDao.scala
    |   |   |   |   |-- UsersDao.scala
    |   |   |   |-- mappings
    |   |   |   |   |-- JsonMappings.scala
    |   |   |   |-- models
    |   |   |   |   |-- Comment.scala
    |   |   |   |   |-- Post.scala
    |   |   |   |   |-- User.scala
    |   |   |   |   |-- package.scala
    |   |   |   |   |-- definitions
    |   |   |   |       |-- CommentsTable.scala
    |   |   |   |       |-- PostsTable.scala
    |   |   |   |       |-- UsersTable.scala
    |   |   |   |-- utils
    |   |   |       |-- Config.scala
    |   |   |       |-- DatabaseConfig.scala
    |   |   |       |-- MigrationConfig.scala
    |   |   |-- scala-2.11
    |   |-- test
    |       |-- java
    |       |-- resources
    |       |-- scala
    |       |   |-- BaseServiceSpec.scala
    |       |   |-- CommentsApiSpec.scala
    |       |   |-- PostsApiSpec.scala
    |       |   |-- UsersApiSpec.scala
    |       |-- scala-2.11

B) Database

I use postgresql, example of configuration of database you can find in

1
src/main/resources/application.conf.example

So create your own application.conf inside

1
src/main/resources/

Schema is as simple as it could be:

images

I use flyway for database migration so you can preview .sql files in

1
src/main/resources/db/migration/

C) Project configuration

Main.scala is an entry point to our application

1
2
3
4
5
6
7
8
9
10
object Main extends App with Config with MigrationConfig with Routes{
  private implicit val system = ActorSystem()
  protected implicit val executor: ExecutionContext = system.dispatcher
  protected val log: LoggingAdapter = Logging(system, getClass)
  protected implicit val materializer: ActorMaterializer = ActorMaterializer()

  migrate()

  Http().bindAndHandle(handler = logRequestResult("log")(routes), interface = httpInterface, port = httpPort)
}

As you see there is initialization of required variables. We should start ActorSystem(), create executor and materializer. There is also binding of http request to our routes. We will go back to it later.

object Main extends many other objects, App of course, but also other like Config.

1
2
3
4
5
6
7
8
9
10
11
trait Config {
  private val config = ConfigFactory.load()
  private val httpConfig = config.getConfig("http")
  private val databaseConfig = config.getConfig("database")
  val httpInterface = httpConfig.getString("interface")
  val httpPort = httpConfig.getInt("port")

  val databaseUrl = databaseConfig.getString("url")
  val databaseUser = databaseConfig.getString("user")
  val databasePassword = databaseConfig.getString("password")
}

I will not copy the documentation: Akka configuration docs

Next file is MigrationConfig.scala:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
trait MigrationConfig extends Config {

  private val flyway = new Flyway()
  flyway.setDataSource(databaseUrl, databaseUser, databasePassword)

  def migrate() = {
    flyway.migrate()
  }

  def reloadSchema() = {
    flyway.clean()
    flyway.migrate()
  }
}

Here we’ve got initialization of flyway object, we setup it with proper database values(they come from Config.scala). There are two methods, one for running migrations and second for reloading schema.

And the last ‘extention’ is Routes.scala

1
2
3
4
5
6
7
8
trait Routes extends ApiErrorHandler with UsersApi with PostsApi with CommentsApi{
  val routes =
    pathPrefix("v1") {
      usersApi ~
      postsApi ~
      commentsApi
    } ~ path("")(getFromResource("public/index.html"))
}

As you suppose this is highest level of our api routes. There is specified some prefix, and default route. Routes are extending some objects but we will go back to them later.

III. Rest Api

Ok so what about that Routes.scala. It is our ‘dispatcher’ which will match http requests to proper actions. Let’s focus on ApiErrorHander which trait Routes extends:

1
2
3
4
5
6
7
8
trait ApiErrorHandler {
  implicit def myExceptionHandler: ExceptionHandler = ExceptionHandler {
    case e: NoSuchElementException =>
      extractUri { uri =>
        complete(HttpResponse(NotFound, entity = s"Invalid id: ${e.getMessage}"))
      }
  }
}

I made this implicit because I want each error, which I suppose to happen in my application, recover in one place. I can do this because each layer of my application is returning Future object, so everything is wrapped up in this monad, our errors will be safely wrapped up and at the end of the world (so in our case ApiErrorHandler) we will recover them. NoSuchElementException will be thrown, for example, when you would like to GET user with non existing id. And because this is implicit all I need is just extend that object, and I have this recover part code in proper scope.

Exception handling in docs: Akka exception handling docs

Now, lets look at Users api:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
trait UsersApi extends JsonMappings{
  val usersApi =
    (path("users") & get ) {
       complete (UsersDao.findAll.map(_.toJson))
    }~
    (path("users"/IntNumber) & get) { id =>
        complete (UsersDao.findById(id).map(_.toJson))
    }~
    (path("users") & post) { entity(as[User]) { user =>
        complete (UsersDao.create(user).map(_.toJson))
      }
    }~
    (path("users"/IntNumber) & put) { id => entity(as[User]) { user =>
        complete (UsersDao.update(user, id).map(_.toJson))
      }
    }~
    (path("users"/IntNumber) & delete) { userId =>
      complete (UsersDao.delete(userId).map(_.toJson))
    }
}

Everything here should be self explanatory: You’ve got some path, rest method definition, and response when Dao method will be completed. Users Dao methods are returning Future so we’ve got to ‘unpack’ it with value by using map and we need to transform result to json. You should notice that UsersApi is extending JsonMappings:

1
2
3
4
5
trait JsonMappings extends DefaultJsonProtocol {
  implicit val userFormat = jsonFormat5(User)
  implicit val postFormat = jsonFormat4(Post)
  implicit val commentFormat = jsonFormat4(Comment)
}

I placed here all json formats objects which will implicitly transform object to json. Without it you will get compilation error because if you see signature of method toJson():

1
def toJson(implicit writer: JsonWriter[T]): JsValue

It expects implicit object of type JsonWriter and this part:

1
implicit val userFormat = jsonFormat5(User)

Creates JsonWriter[User] and JsonReader[User] so our api methods can transform scala object to json and json to scala object.

Ok last layer is DAO. Let’s look at UsersDao:

1
2
3
4
5
6
7
8
9
10
object UsersDao extends BaseDao{
  def findAll: Future[Seq[User]] = usersTable.result
  def findById(userId: UserId): Future[User] = usersTable.filter(_.id === userId).result.head
  def create(user: User): Future[UserId] = usersTable returning usersTable.map(_.id) += user
  def update(newUser: User, userId: UserId): Future[Int] = usersTable.filter(_.id === userId)
    .map(user => (user.username, user.password, user.gender, user.age))
    .update((newUser.userName, newUser.password, newUser.gender, newUser.age))

  def delete(userId: UserId): Future[Int] = usersTable.filter(_.id === userId).delete
}

This should look familiar to you, even if you were not using slick. We are creating sql queries and send them to db. When you will look in docs you will see that each slick statement like this one for example:

1
usersTable.filter(_.id === userId).result.head

Should be done within db.run() method (where db is config variable from DatabaseConfig.scala). So those Dao methods should look like this:

1
db.run(usersTable.filter(_.id === userId).result.head)

etc. It would be tedious to write this boilerplate code. This is why we have implicits :)

Look at BaseDao which UsersDao extends:

1
2
3
4
5
6
7
8
9
10
11
12
trait BaseDao extends DatabaseConfig {
  val usersTable = TableQuery[UsersTable]
  val postsTable = TableQuery[PostsTable]
  val commentsTable = TableQuery[CommentsTable]

  protected implicit def executeFromDb[A](action: SqlAction[A, NoStream, _ <: slick.dbio.Effect]): Future[A] = {
    db.run(action)
  }
  protected implicit def executeReadStreamFromDb[A](action: FixedSqlStreamingAction[Seq[A], A, _ <: slick.dbio.Effect]): Future[Seq[A]] = {
    db.run(action)
  }
}

We’ve got here initialization of TableQuery objects, which are representation of tables in db, and some implicit methods. Those methods transforms result of other methods. But which one? Those one who are explicitly said that should return Future[A] or Future[Seq[A]] but instead they are returning SqlAction or FixedSqlStreamingAction(Which are results of building query in slick, so our DAO methods are example of those). So those methods apply db.run to this SqlAction and FixedSqlStreamingAction objects and this is how we avoid writing db.run(…) each time we want to call dao method.

IV. Tests

I tested those api methods using scalatest. Check it out ;)

V. Conclusion

Akka http is additional layer on top of akka which helps you communicate with actor system via Http requests. With a little effort you can build webservice easily. Akka http is very flexible and lightweight so it is not some kind of ‘fullstack’ framework but maybe you should consider using it in your future project.

Thanks for checking out this post, if you have any questions, feel free to comment or contact ;)

Comments