From 08b29984953d9c319247b8dee3a667ab0825dd00 Mon Sep 17 00:00:00 2001 From: afiskon Date: Thu, 13 Aug 2015 18:40:08 +0300 Subject: [PATCH] SubCut usage example --- build.sbt | 1 + .../eax/finagle_example/FinagleExample.scala | 8 +++ .../FinagleServiceExample.scala | 58 +++++++++++-------- .../services/KeyValueStorage.scala | 29 ++++++++++ .../eax/finagle_example/utils/package.scala | 28 +++++++++ .../eax/finagle_example/tests/RestSpec.scala | 8 +++ 6 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 src/main/scala/me/eax/finagle_example/services/KeyValueStorage.scala create mode 100644 src/main/scala/me/eax/finagle_example/utils/package.scala diff --git a/build.sbt b/build.sbt index 594cd6c..fcefb18 100644 --- a/build.sbt +++ b/build.sbt @@ -8,6 +8,7 @@ libraryDependencies ++= Seq( "com.twitter" %% "finagle-http" % "6.27.0", "ch.qos.logback" % "logback-classic" % "1.1.3", "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0", + "com.escalatesoft.subcut" %% "subcut" % "2.1", "org.scalatest" %% "scalatest" % "2.2.4" % "test", "org.pegdown" % "pegdown" % "1.5.0" % "test" // for scalatest html reports ) diff --git a/src/main/scala/me/eax/finagle_example/FinagleExample.scala b/src/main/scala/me/eax/finagle_example/FinagleExample.scala index c291e0d..939d599 100644 --- a/src/main/scala/me/eax/finagle_example/FinagleExample.scala +++ b/src/main/scala/me/eax/finagle_example/FinagleExample.scala @@ -1,10 +1,18 @@ package me.eax.finagle_example +import com.escalatesoft.subcut.inject.NewBindingModule._ import com.twitter.finagle._ import com.twitter.finagle.http.{Http => _} import com.twitter.util._ +import me.eax.finagle_example.services._ object FinagleExample extends App { + implicit val bindings = newBindingModule { module => + import module._ + + bind[KeyValueStorage] toSingle new KeyValueStorageImpl + } + val service = new FinagleServiceExample val server = Http.serve(":8080", service) Await.ready(server) diff --git a/src/main/scala/me/eax/finagle_example/FinagleServiceExample.scala b/src/main/scala/me/eax/finagle_example/FinagleServiceExample.scala index c4a2ef8..ed32813 100644 --- a/src/main/scala/me/eax/finagle_example/FinagleServiceExample.scala +++ b/src/main/scala/me/eax/finagle_example/FinagleServiceExample.scala @@ -1,48 +1,58 @@ package me.eax.finagle_example +import me.eax.finagle_example.services._ +import me.eax.finagle_example.utils._ + +import com.escalatesoft.subcut.inject.{Injectable, BindingModule} import com.twitter.finagle.Service import com.twitter.finagle.http.Response -import com.twitter.util.Future +import com.twitter.util.{Future => TwitterFuture} import org.jboss.netty.handler.codec.http._ import org.jboss.netty.util.CharsetUtil import com.typesafe.scalalogging._ import org.slf4j.LoggerFactory -import scala.collection.concurrent.TrieMap +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global + +class FinagleServiceExample(implicit val bindingModule: BindingModule) + extends Service[HttpRequest, HttpResponse] with Injectable { -class FinagleServiceExample extends Service[HttpRequest, HttpResponse] { - private val kv = TrieMap.empty[String, String] + private val kv = inject[KeyValueStorage] private val logger = Logger(LoggerFactory.getLogger(this.getClass)) logger.info(s"service started") - def apply(req: HttpRequest): Future[HttpResponse] = { - Future { - val resp = Response(req.getProtocolVersion, HttpResponseStatus.OK) - val key = req.getUri + def apply(req: HttpRequest): TwitterFuture[HttpResponse] = { + val resp = Response(req.getProtocolVersion, HttpResponseStatus.OK) + val key = req.getUri - req.getMethod match { - case HttpMethod.GET => - logger.debug(s"reading $key") - kv.get(key) match { + req.getMethod match { + case HttpMethod.GET => + logger.debug(s"reading $key") + for { + optValue <- kv.get(key) + } yield { + optValue match { case None => resp.setStatus(HttpResponseStatus.NOT_FOUND) case Some(value) => resp.setContentString(value) } - case HttpMethod.POST => - logger.debug(s"writing $key") - val value = req.getContent.toString(CharsetUtil.UTF_8) - kv.update(key, value) - case HttpMethod.DELETE => - logger.debug(s"deleting $key") - kv.remove(key) - case _ => - logger.error(s"bad request: $req") - resp.setStatus(HttpResponseStatus.BAD_REQUEST) - } - resp + resp + } + case HttpMethod.POST => + logger.debug(s"writing $key") + val value = req.getContent.toString(CharsetUtil.UTF_8) + kv.update(key, value).map(_ => resp) + case HttpMethod.DELETE => + logger.debug(s"deleting $key") + kv.remove(key).map(_ => resp) + case _ => + logger.error(s"bad request: $req") + resp.setStatus(HttpResponseStatus.BAD_REQUEST) + Future.successful(resp) } } } diff --git a/src/main/scala/me/eax/finagle_example/services/KeyValueStorage.scala b/src/main/scala/me/eax/finagle_example/services/KeyValueStorage.scala new file mode 100644 index 0000000..34e1bf2 --- /dev/null +++ b/src/main/scala/me/eax/finagle_example/services/KeyValueStorage.scala @@ -0,0 +1,29 @@ +package me.eax.finagle_example.services + +import scala.collection.concurrent.TrieMap +import scala.concurrent._ + +trait KeyValueStorage { + def get(key: String): Future[Option[String]] + def update(key: String, value: String): Future[Unit] + def remove(key: String): Future[Unit] +} + +class KeyValueStorageImpl extends KeyValueStorage { + private val kv = TrieMap.empty[String, String] + + def get(key: String): Future[Option[String]] = { + val result = kv.get(key) + Future.successful(result) + } + + def update(key: String, value: String): Future[Unit] = { + val result = kv.update(key, value) + Future.successful(result) + } + + def remove(key: String): Future[Unit] = { + kv.remove(key) + Future.successful({}) + } +} \ No newline at end of file diff --git a/src/main/scala/me/eax/finagle_example/utils/package.scala b/src/main/scala/me/eax/finagle_example/utils/package.scala new file mode 100644 index 0000000..12d91ec --- /dev/null +++ b/src/main/scala/me/eax/finagle_example/utils/package.scala @@ -0,0 +1,28 @@ +package me.eax.finagle_example + +import scala.util._ +import scala.concurrent._ + +package object utils { + implicit def scalaToTwitterTry[T](t: Try[T]): com.twitter.util.Try[T] = t match { + case Success(r) => com.twitter.util.Return(r) + case Failure(ex) => com.twitter.util.Throw(ex) + } + + implicit def twitterToScalaTry[T](t: com.twitter.util.Try[T]): Try[T] = t match { + case com.twitter.util.Return(r) => Success(r) + case com.twitter.util.Throw(ex) => Failure(ex) + } + + implicit def scalaToTwitterFuture[T](f: Future[T])(implicit ec: ExecutionContext): com.twitter.util.Future[T] = { + val promise = com.twitter.util.Promise[T]() + f.onComplete(promise update _) + promise + } + + implicit def twitterToScalaFuture[T](f: com.twitter.util.Future[T]): Future[T] = { + val promise = Promise[T]() + f.respond(promise complete _) + promise.future + } +} diff --git a/src/test/scala/me/eax/finagle_example/tests/RestSpec.scala b/src/test/scala/me/eax/finagle_example/tests/RestSpec.scala index f7d2ded..f60ef29 100644 --- a/src/test/scala/me/eax/finagle_example/tests/RestSpec.scala +++ b/src/test/scala/me/eax/finagle_example/tests/RestSpec.scala @@ -1,14 +1,22 @@ package me.eax.finagle_example.tests +import com.escalatesoft.subcut.inject.NewBindingModule._ import com.twitter.finagle.{Http, Service} import com.twitter.util.Await import me.eax.finagle_example.FinagleServiceExample +import me.eax.finagle_example.services.{KeyValueStorageImpl, KeyValueStorage} import org.jboss.netty.buffer.ChannelBuffers import org.jboss.netty.handler.codec.http._ import org.jboss.netty.util.CharsetUtil import org.scalatest._ class RestSpec extends FunSpec with Matchers { + implicit val bindings = newBindingModule { module => + import module._ + + bind[KeyValueStorage] toSingle new KeyValueStorageImpl + } + val service = new FinagleServiceExample val server = Http.serve(":8080", service) val client: Service[HttpRequest, HttpResponse] = Http.newService("localhost:8080")