REST API and CRUD Operations with Akka HTTP

REST API and CRUD Operations with Akka HTTP

Introduction

        In this post, we will be creating an Akka HTTP Application with GET, POST, PUT and DELETE APIs for CRUD operations on an Employee Domain class. All data will be kept in memory in a Vector.

Akka HTTP

        The Akka HTTP modules implement a full server- and client-side HTTP stack on top of akka-actor and akka-stream. It’s not a web-framework but rather a more general toolkit for providing and consuming HTTP-based services. 
Most important features that Akka HTTP provides are:               
  1. Asynchronous processing 
  2. Non blocking I/O operations 
For more details on Akka HTTP, you can check: Akka HTTP Website.

Requirements to Run the Application:

1. Intellij IDE with SBT and Scala Plugins

In this tutorial I will help you to build an Akka HTTP Application in a few simple steps.

Step 1: Dependencies in build.sbt

Since the code is in Scala, I will be using sbt for build related setup. sbt (Scala Build Tool, formerly Simple Build Tool) is an open source build tool for Scala and Java projects, similar to Java's Maven and Ant. I am using the following in this tutorial:
  • ScalaVersion = 2.12.2
  • AkkaVersion = 2.4.18
  • AkkaHttpVersion = 10.0.6
  • Json4sVersion = 3.5.2 
These have to be set in the file: build.sbt
Here is the full build.sbt:
enablePlugins(JavaServerAppPackaging)

name := "BasicAkkaHTTP"

version := "1.0"

organization := "com.aj"

scalaVersion := "2.12.2"

resolvers ++= Seq("Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/",
                  Resolver.bintrayRepo("hseeberger", "maven"))

libraryDependencies ++= {
  val AkkaVersion = "2.4.18"
  val AkkaHttpVersion = "10.0.6"
  val Json4sVersion = "3.5.2"
  Seq(
    "com.typesafe.akka" %% "akka-slf4j"      % AkkaVersion,
    "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion,
    "ch.qos.logback" % "logback-classic" % "1.2.3",
    "org.json4s"        %% "json4s-native"   % Json4sVersion,
    "org.json4s"        %% "json4s-ext"      % Json4sVersion,
    "de.heikoseeberger" %% "akka-http-json4s" % "1.16.0"
  )
}

// Assembly settings
mainClass in Global := Some("com.aj.basicakkahttp.Main")

assemblyJarName in assembly := "BasicAkkaHTTP.jar"

Step 2: Application Configuration

Server host, port and logging related configurations are added in application.conf. Here is the full file application.conf:
akka {
  loglevel = INFO
  stdout-loglevel = INFO
  loggers = ["akka.event.slf4j.Slf4jLogger"]
  default-dispatcher {
    fork-join-executor {
      parallelism-min = 8
    }
  }
  test {
    timefactor = 1
  }
}

http {
  host = "0.0.0.0"
  host = ${?HOST}
  port = 4000
  port = ${?PORT}
}

Step 3: Create the Entity class

In this post, I am using a simple entity Employee with attributes: ID, NAME and DEPARTMENT Here is the Entity Model, Employee.scala:
package com.aj.basicakkahttp.domain

case class Employee(id: String, name: String, department: String)

Step 4: Create the Service Layer

Service class creates a Vector to keep all the employee data in memory. It also has implementations for the following operations:
  1. Create an Employee
  2. Get/Read an Employee
  3. Update an Employee
  4. Delete an Employee 
Here is the code for EmployeeService.scala:
package com.aj.basicakkahttp.service

import com.aj.basicakkahttp.domain.{Employee, EmployeeUpdate}

import scala.concurrent.{ExecutionContext, Future}

class EmployeeService(implicit val executionContext: ExecutionContext) {

  var employees = Vector.empty[Employee]

  def createEmployee(employee: Employee): Future[Option[String]] = Future {
    employees.find(_.id == employee.id) match {
      case Some(q) => None
      case None =>
        employees = employees :+ employee
        Some(employee.id)
    }
  }

  def getEmployee(id: String): Future[Option[Employee]] = Future {
    employees.find(_.id == id)
  }

  def updateEmployee(id: String, update: EmployeeUpdate): Future[Option[Employee]] = {

    def updateEntity(employee: Employee): Employee = {
      val title = update.name.getOrElse(employee.name)
      val text = update.department.getOrElse(employee.department)
      Employee(id, title, text)
    }

    getEmployee(id).flatMap { maybeEmployee =>
      maybeEmployee match {
        case None => Future { None }
        case Some(employee) =>
          val updatedEmployee = updateEntity(employee)
          deleteEmployee(id).flatMap { _ =>
            createEmployee(updatedEmployee).map(_ => Some(updatedEmployee))
          }
      }
    }
  }

  def deleteEmployee(id: String): Future[Unit] = Future {
    employees = employees.filterNot(_.id == id)
  }


}

Step 5: Create Resource Class with RESTful APIs for CRUD operations on Employee Entity

The EmployeeResource class has the following APIs:
  1. GET API to get employee by ID
  2. POST API to create an employee
  3. PUT API to update an employee
  4. DELETE API to delete an employee by ID
Here is the code for EmployeeResource.scala:
package com.aj.basicakkahttp.resource

import akka.http.scaladsl.server.Route

import com.aj.basicakkahttp.domain.{Employee, EmployeeUpdate}
import com.aj.basicakkahttp.routing.MyResource
import com.aj.basicakkahttp.service.EmployeeService

trait EmployeeResource extends MyResource {

  val employeeService: EmployeeService

  def employeeRoutes: Route = pathPrefix("employee") {
    pathEnd {
      post {
        entity(as[Employee]) { employee =>
          completeWithLocationHeader(
            resourceId = employeeService.createEmployee(employee),
            ifDefinedStatus = 201, ifEmptyStatus = 409)
          }
        }
    } ~
    path(Segment) { id =>
      get {
        complete(employeeService.getEmployee(id))
      } ~
      put {
        entity(as[EmployeeUpdate]) { update =>
          complete(employeeService.updateEmployee(id, update))
        }
      } ~
      delete {
        complete(employeeService.deleteEmployee(id))
      }
    }

  }
}

Step 6: Main Object and Rest Interface

Main Object is the Main Class and entry point of the application. Here we get the host and port from the config file application.conf and bind them to the APIs. Here is the code for Main.scala:
package com.aj.basicakkahttp

import akka.actor._
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.util.Timeout
import com.typesafe.config.ConfigFactory

import scala.concurrent.duration._

object Main extends App with RestInterface {
  val config = ConfigFactory.load()
  val host = config.getString("http.host")
  val port = config.getInt("http.port")

  implicit val system = ActorSystem("BasicAkkaHTTP")
  implicit val materializer = ActorMaterializer()


  implicit val executionContext = system.dispatcher
  implicit val timeout = Timeout(10 seconds)

  val api = routes

  Http().bindAndHandle(handler = api, interface = host, port = port) map { binding =>
    println(s"REST interface bound to ${binding.localAddress}") } recover { case ex =>
    println(s"REST interface could not bind to $host:$port", ex.getMessage)
  }
}
In RestInterface.scala, we configure the routes and service used in this application. Here is the code for RestInterface.scala:
package com.aj.basicakkahttp

import akka.http.scaladsl.server.Route
import com.aj.basicakkahttp.resource.EmployeeResource
import com.aj.basicakkahttp.service.EmployeeService

import scala.concurrent.ExecutionContext

trait RestInterface extends Resources {

  implicit def executionContext: ExecutionContext

  lazy val employeeService = new EmployeeService

  val routes: Route = employeeRoutes

}

trait Resources extends EmployeeResource

Run Application:

1. To run application in your IDE use:
    Main class: com.aj.basicakkahttp.Main

2. To run JAR from Intellij:
    Build jar by running assembly in SBT Tab in Intellij
    Run JAR by using java -jar command in the location: target/scala-2.12 of the Project folder location. For me it is:
D:\projects\gitprojects\BasicAkkaHTTP\target\scala-2.12>java -jar BasicAkkaHTTP.jar

API calls and results:

1. POST API to create an employee
    JSON Request Body:
  {
   "id": "1",
   "name": "Mark",
   "department": "Technology"
  }
2. GET API to get employee by ID
    http://localhost:4000/employee/1

3. PUT  API to update an employee
    http://localhost:4000/employee/1
    JSON Request Body:
    {
        "name": "Jim",
        "department": "Accounts"
    }
4. DELETE API to delete an employee by ID
    http://localhost:4000/employee/1

Conclusion and GitHub link:

    In this post I have shown you how you can create an Akka HTTP application and perform CRUD operations using RESTful APIs. The code used in this post is available on GitHub.
    Learn the most popular and trending technologies like Machine Learning, Angular 5, Internet of Things (IoT), Akka HTTP, Play Framework, Dropwizard, Docker, Elastic Stack, Netflix Eureka, Netflix Zuul, Spring Cloud, Spring Boot, Flask and RESTful Web Service integration with MongoDB, Kafka, Redis, Aerospike, MySQL DB in simple steps by reading my most popular blog posts at Software Developer Central.
    If you like my post, please feel free to share it using the share button just below this paragraph or next to the heading of the post. You can also tweet with #SoftwareDeveloperCentral on Twitter. To get a notification on my latest posts or to keep the conversation going, you can follow me on Twitter. Please leave a note below if you have any questions or comments.

Comments

Popular Posts

REST API using Play Framework with Java

Asynchronous Processing (@Async) in Spring Boot

Elasticsearch, Logstash, Kibana Tutorial: Load MySQL Data into Elasticsearch