Skip to content

Instantly share code, notes, and snippets.

@Technius
Last active March 23, 2017 06:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Technius/4fddfc9e33b6c2fff13cbea5e4c67d53 to your computer and use it in GitHub Desktop.
Save Technius/4fddfc9e33b6c2fff13cbea5e4c67d53 to your computer and use it in GitHub Desktop.
Note Service in Akka-HTTP
package example
sealed trait ApiResult
object ApiResult {
/* Returns all notes */
case class ListNotes(notes: Seq[Note]) extends ApiResult
/* Returns a specific note found */
case class FoundNote(note: Note) extends ApiResult
/* Indicates when a specified note cannot be found */
case class NoteNotFound(id: Int) extends ApiResult
/* Indicates when a note is successfully created */
case class CreatedNote(note: Note) extends ApiResult
/* Indicates when a note is successfully updated */
case class UpdatedNote(updatedNote: Note) extends ApiResult
/* Indicates when a note is sucessfully deleted. */
case class DeletedNote(deletedNote: Note) extends ApiResult
}
package example
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
class InMemoryNoteRepository extends NoteRepository {
private[this] val notes = scala.collection.concurrent.TrieMap[Int, Note]()
override def list(): Future[Seq[Note]] = Future(notes.values.toSeq)
override def find(id: Int) = Future(notes.get(id))
override def create(title: String, content: String) = Future {
val id = notes.keys.foldLeft(0)(_ max _) + 1
val n = Note(id, title, content)
notes(id) = n
n
}
override def update(id: Int, title: Option[String], content: Option[String]) =
Future {
val nOpt = notes.get(id).map { note =>
note.copy(title = title.getOrElse(note.title), content = content.getOrElse(note.content))
}
nOpt foreach { note =>
notes(id) = note
}
nOpt
}
override def delete(id: Int) = Future(notes.remove(id))
}
object Routes extends Routes {
val noteService = new NoteService { val repo = new InMemoryNoteRepository }
}
package example
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
case class Note(id: Int, title: String, content: String)
// Don't mix model and behavior in the same file, please. I'm just being lazy.
trait NoteRepository {
def list(): Future[Seq[Note]]
def find(id: Int): Future[Option[Note]]
def create(title: String, content: String): Future[Note]
def update(id: Int, title: Option[String], content: Option[String]): Future[Option[Note]]
def delete(id: Int): Future[Option[Note]]
}
trait NoteService {
import ApiResult._
def repo: NoteRepository
def list: Future[ApiResult] =
repo.list().map(ListNotes.apply _)
def find(id: Int): Future[ApiResult] =
repo.find(id).map(noteOp(id)(FoundNote.apply _))
def create(title: String, content: String): Future[ApiResult] =
repo.create(title, content).map(CreatedNote.apply _)
def update(id: Int, title: Option[String], content: Option[String]): Future[ApiResult] =
repo.update(id, title, content).map(noteOp(id)(UpdatedNote.apply _))
def delete(id: Int): Future[ApiResult] =
repo.delete(id).map(noteOp(id)(DeletedNote.apply _))
/*
* Helper for returning NoteNotFound if an Option is None, or passing the
* found note to f otherwise.
*/
private def noteOp[T](id: Int)(f: Note => ApiResult): Option[Note] => ApiResult =
opt => opt.map(f).getOrElse(NoteNotFound(id))
}
package example
import akka.http.scaladsl._
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
object NoteServer {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
val host = "0.0.0.0"
val port = 1234
val fut = Http().bindAndHandle(Routes.notesRoute, host, port)
println("Started")
}
}
package example
import scala.concurrent.ExecutionContext.Implicits.global
import akka.http.scaladsl.server.Directives._
import upickle.default._
trait Routes {
def noteService: NoteService
val notesRoute =
pathPrefix("notes") {
(path(IntNumber)) { id =>
get {
complete {
noteService.find(id).map(write(_))
}
} ~
delete {
complete {
noteService.delete(id).map(write(_))
}
} ~
(post & parameters('title.?, 'content.?)) { (title, content) =>
complete {
noteService.update(id, title, content).map(write(_))
}
}
} ~
pathEndOrSingleSlash {
get {
complete {
noteService.list.map(write(_))
}
} ~
(post & parameters('title, 'content)) { (title, content) =>
complete {
noteService.create(title, content).map(write(_))
}
}
}
}
}
package example
import akka.http.scaladsl._
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
object Server {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
val host = "0.0.0.0"
val port = 1234
val fut = Http().bindAndHandle(Routes.notesRoute, host, port)
println("Started")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment