GithubHelp home page GithubHelp logo

finagle / finagle-serial Goto Github PK

View Code? Open in Web Editor NEW
62.0 15.0 9.0 436 KB

Create Finagle servers and clients that use the serialization library of your choice!

Home Page: https://twitter.com/finagle

License: Apache License 2.0

Scala 99.40% Thrift 0.60%

finagle-serial's Introduction

Finagle Serial

Build status Coverage Status

Finagle Serial supports the creation of Finagle servers and clients that use Scala (or Java) libraries for serialization instead of IDL-based systems like Apache Thrift or Google's Protobuf. It's designed to make it easy to construct Finagle services that take arbitrary Scala types as inputs and outputs, with minimal boilerplate.

Finagle Serial uses Mux as its session-layer protocol, with object serialization (the presentation layer, to use the terminology of the OSI model) supplied by a pluggable wrapper for the serialization library of your choice. We currently provide support for Scodec.

Quickstart

Let's start with some simple case classes:

case class User(name: String)
case class Greeting(u: User) {
  override def toString = s"Hello, ${u.name}!"
}

Now we can write a Finagle service that greets a user:

import com.twitter.finagle.Service
import com.twitter.util.Future

object GreetUser extends Service[User, Greeting] {
  def apply(u: User) = Future.value(Greeting(u))
}

Now suppose we want to make this greeting service available on the network. All we need to do is pick a serialization backend (we'll use Scodec here), and provide codecs for our input and output types (see the Scodec documentation for an explanation of the use of variableSizeBits, uint24, and utf8 here):

import io.github.finagle.serial.scodec.ScodecSerial
import java.net.InetSocketAddress
import scodec.Codec
import scodec.codecs._

implicit val userCodec: Codec[User] = variableSizeBits(uint24, utf8).as[User]
implicit val greetingCodec: Codec[Greeting] = userCodec.as[Greeting]

val protocol = ScodecSerial[User, Greeting]

val server = protocol.serve(new InetSocketAddress(8123), GreetUser)
val client = protocol.newService("localhost:8123")

And now we can call our server from our client:

client(User("Mary")).onSuccess { greeting =>
  println(greeting)
}

That's all! No plugins, no IDLs, no code generation, and very little boilerplate.

Installation

Serial is brand new and is not published to Maven Central at the moment (it will be soon), but for now you can check out this repository, run sbt +publish-local, and then add the following dependency to your project:

libraryDependencies += "io.github.finagle" %% "finagle-serial-scodec" % "0.0.1"

Error handling

The most straightforward way to take care of application error handling is simply to represent the possibility of error in your service's return type, and then provide the appropriate codecs. For example, a simple integer parsing service might have the following implementation:

object IntParser extends Service[String, Either[NumberFormatException, Int]] {
  def apply(s: String) = Future(
    try Right(s.toInt) catch {
      case e: NumberFormatException => Left(e)
    }
  )
}

Now you just need to tell your serialization backend how to encode String and Either[NumberFormatException, Int] and you're done.

It's also possible (and sometimes more convenient) to use the service's Future to represent the possibility of error. This allows you to write the following:

object IntParser extends Service[String, Int] {
  def apply(s: String) = Future(s.toInt)
}

Now depending on the serialization backend you choose, one of two things will happen when you call this service with invalid input. If that backend supports serializing NumberFormatException, you'll get back a NumberFormatException (wrapped in a Try, of course). If the backend doesn't know how to serialize the exception, you'll get a io.github.finagle.serial.ApplicationError instead (also wrapped in a Try).

Consult your serialization backend to see which exceptions it can serialize—it may support adding your own, as well. For example, the default Scodec backend knows how to serialize a few commonly-used exceptions from the Java standard library, and you can easily add user-defined exceptions, or other exceptions it doesn't know about.

There are three more exceptional kinds of exceptions that may be returned from a Serial service:

  • A io.github.finagle.serial.CodecError represents an encoding error at the presentation layer. This probably indicates a problem with one of your codecs, and a well-behaved serialization backend should provide an error message that clearly indicates the source of the issue.
  • A com.twitter.finagle.mux.ServerError indicates an encoding error at the session layer. This almost certainly isn't something you're responsible for (whether you're implementing a service or a serialization backend).
  • A com.twitter.finagle.mux.ServerApplicationError indicates an unhandled application error. A well-behaved serialization backend implementation shouldn't return these, but instead should return a CodecError or ApplicationError (assuming it can't return the exception thrown by your service).

Testing

We provide a SerialIntegrationTest that makes it easy to use ScalaCheck to help verify that your serialization backend implementation is working correctly. For example, the following is a simplified (but complete) version of part of the integration testing for the Scodec backend:

import _root_.scodec._
import _root_.scodec.codecs._
import com.twitter.util.Await
import io.github.finagle.serial.test.SerialIntegrationTest
import org.scalacheck.{Arbitrary, Gen}
import org.scalatest.FunSuite

class ScodecIntegrationTest extends FunSuite with ScodecSerial with SerialIntegrationTest {
  implicit val intCodec: Codec[Int] = int32
  implicit val stringCodec: Codec[String] = variableSizeBits(uint24, utf8)

  case class Foo(i: Int, s: String)

  implicit val fooCodec: Codec[Foo] = (uint8 :: stringCodec).as[Foo]

  implicit val fooArbitrary: Arbitrary[Foo] = Arbitrary(
    for {
      i <- Gen.choose(0, 255)
      s <- Gen.alphaStr
    } yield Foo(i, s)
  )

  test("A service that doubles an integer should work on all integers") {
    testFunctionService[Int, Int](_ * 2)
  }

  test("A service that returns the length of a string should work on all strings") {
    testFunctionService[String, Int](s => s.length)
  }

  test("A service that changes a case class should work on all instances") {
    testFunctionService[Foo, Foo] {
      case Foo(i, s) => Foo(i % 128, s * 2)
    }
  }
}

Check the test project documentation for more information about these tools.

Benchmarks

We also provide a very preliminary benchmark project that uses JMH to compare the performance of the Scodec backend to a similar Finagle Thrift service. In our initial testing, the Scodec backend manages about 46% of the throughput of the Thrift implementation:

i.g.f.s.RoundTripThriftSmallBenchmark.test    thrpt       20  22422.923 ± 1098.352  ops/s
i.g.f.s.ScodecSmallRTBenchmark.test           thrpt       20  10335.675 ± 190.058  ops/s

These benchmarks (even more than most benchmarks) should be taken with a grain of salt, but will be refined as the project matures.

License

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

finagle-serial's People

Contributors

travisbrown avatar vkostyukov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

finagle-serial's Issues

Service test doesn't work with types involving lists

I'm able to use testFunctionService for signatures involving non-container types but when I use List or Vector there appear to be serialization errors that don't occur when I test encode/decode manually.

package blah

import io.github.finagle.serial.scodec.ScodecSerial
import io.github.finagle.serial.tests.SerialIntegrationTest
import org.scalacheck._
import org.scalatest.FunSuite
import scodec.Attempt.Successful
import scodec.bits.BitVector
import scodec.codecs._
import scodec.{Codec, DecodeResult}

class ListSpec extends FunSuite with ScodecSerial with SerialIntegrationTest {

  implicit val listCodec = list(utf8_32)

  implicit val intCodec = int32

  implicit val lists: Arbitrary[List[String]] = Arbitrary {
    Gen.nonEmptyContainerOf[List, String](Gen.alphaStr)
  }

  test("List encode decode") {
    check((p: List[String]) => {
      listCodec.encode(p).flatMap(listCodec.decode) === Successful(DecodeResult(p, BitVector.empty))
      }
    )
  }

  test("List service test") {
    testFunctionService[List[String], Int]((p1: List[String]) => p1.length)
  }

}

The first test passes but the second fails with this (technically I had to modify a future with onFailure to dump this, but it is the root cause):

io.github.finagle.serial.CodecError: cannot acquire 32 bits from a vector that contains 7 bits
    at io.github.finagle.serial.scodec.ScodecSerial$fresh$macro$49$1.from(ScodecSerial.scala:27)
    at io.github.finagle.serial.scodec.ScodecSerial$fresh$macro$49$1.from(ScodecSerial.scala:27)
    at scodec.Transformer$$anon$7$$anonfun$apply$12.apply(Transform.scala:121)
    at scodec.DecodeResult.map(DecodeResult.scala:17)
    at scodec.Codec$$anon$2$$anonfun$decode$1.apply(Codec.scala:201)
    at scodec.Codec$$anon$2$$anonfun$decode$1.apply(Codec.scala:201)
    at scodec.Attempt$Successful.map(Attempt.scala:92)
    at scodec.Codec$$anon$2.decode(Codec.scala:201)
    at scodec.Codec$$anon$2.decode(Codec.scala:201)
    at scodec.codecs.ToCoproductCodecs$$anon$8$$anon$1.decode(CoproductCodec.scala:108)
    at scodec.codecs.ToCoproductCodecs$$anon$8$$anonfun$1$$anon$2.decode(CoproductCodec.scala:120)
    at scodec.Decoder$$anon$2.decode(Decoder.scala:46)
    at scodec.GenCodec$$anon$2.decode(GenCodec.scala:76)
    at scodec.Decoder$$anon$3$$anonfun$decode$2.apply(Decoder.scala:54)
    at scodec.Decoder$$anon$3$$anonfun$decode$2.apply(Decoder.scala:54)
    at scodec.Attempt$Successful.flatMap(Attempt.scala:94)
    at scodec.Decoder$$anon$3.decode(Decoder.scala:54)
    at scodec.Decoder$$anon$3$$anonfun$decode$2.apply(Decoder.scala:54)
    at scodec.Decoder$$anon$3$$anonfun$decode$2.apply(Decoder.scala:54)
    at scodec.Attempt$Successful.flatMap(Attempt.scala:94)
    at scodec.Decoder$$anon$3.decode(Decoder.scala:54)
    at scodec.Decoder$$anon$3$$anonfun$decode$2.apply(Decoder.scala:54)
    at scodec.Decoder$$anon$3$$anonfun$decode$2.apply(Decoder.scala:54)
    at scodec.Attempt$Successful.flatMap(Attempt.scala:94)
    at scodec.Decoder$$anon$3.decode(Decoder.scala:54)
    at scodec.codecs.CoproductCodec$Discriminated.decode(CoproductCodec.scala:60)
    at scodec.Decoder$$anon$4.decode(Decoder.scala:62)
    at scodec.GenCodec$$anon$2.decode(GenCodec.scala:76)
    at scodec.Codec$$anon$1.decode(Codec.scala:191)
    at scodec.Codec$$anon$2.decode(Codec.scala:201)
    at io.github.finagle.serial.scodec.ScodecSerial$class.decodeRep(ScodecSerial.scala:103)
    at com.chartboost.protocol.request.ListSpec.decodeRep(ListSpec.scala:12)
    at com.chartboost.protocol.request.ListSpec.decodeRep(ListSpec.scala:12)
    at io.github.finagle.serial.Serial$Client$$anon$4$$anonfun$apply$1$$anonfun$apply$2.apply(Serial.scala:161)
    at io.github.finagle.serial.Serial$Client$$anon$4$$anonfun$apply$1$$anonfun$apply$2.apply(Serial.scala:160)
    at com.twitter.util.Future$$anonfun$flatMap$1.apply(Future.scala:986)
    at com.twitter.util.Future$$anonfun$flatMap$1.apply(Future.scala:985)
    at com.twitter.util.Promise$Transformer.liftedTree1$1(Promise.scala:112)
    at com.twitter.util.Promise$Transformer.k(Promise.scala:112)
    at com.twitter.util.Promise$Transformer.apply(Promise.scala:122)
    at com.twitter.util.Promise$Transformer.apply(Promise.scala:103)
    at com.twitter.util.Promise$$anon$1.run(Promise.scala:381)
    at com.twitter.concurrent.LocalScheduler$Activation.run(Scheduler.scala:178)
    at com.twitter.concurrent.LocalScheduler$Activation.submit(Scheduler.scala:136)
    at com.twitter.concurrent.LocalScheduler.submit(Scheduler.scala:207)
    at com.twitter.concurrent.Scheduler$.submit(Scheduler.scala:92)
    at com.twitter.util.Promise.runq(Promise.scala:350)
    at com.twitter.util.Promise.updateIfEmpty(Promise.scala:721)
    at com.twitter.util.Promise.update(Promise.scala:694)
    at com.twitter.util.Promise.setValue(Promise.scala:670)
    at com.twitter.concurrent.AsyncQueue.offer(AsyncQueue.scala:111)
    at com.twitter.finagle.netty3.transport.ChannelTransport.handleUpstream(ChannelTransport.scala:55)
    at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
    at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendUpstream(DefaultChannelPipeline.java:791)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:296)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.unfoldAndFireMessageReceived(FrameDecoder.java:462)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:443)
    at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:303)
    at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:70)
    at com.twitter.finagle.mux.transport.Netty3Framer$Framer.handleUpstream(Netty3Framer.scala:31)
    at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
    at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendUpstream(DefaultChannelPipeline.java:791)
    at org.jboss.netty.channel.SimpleChannelHandler.messageReceived(SimpleChannelHandler.java:142)
    at com.twitter.finagle.netty3.channel.ChannelStatsHandler.messageReceived(ChannelStatsHandler.scala:68)
    at org.jboss.netty.channel.SimpleChannelHandler.handleUpstream(SimpleChannelHandler.java:88)
    at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
    at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendUpstream(DefaultChannelPipeline.java:791)
    at org.jboss.netty.channel.SimpleChannelHandler.messageReceived(SimpleChannelHandler.java:142)
    at com.twitter.finagle.netty3.channel.ChannelRequestStatsHandler.messageReceived(ChannelRequestStatsHandler.scala:32)
    at org.jboss.netty.channel.SimpleChannelHandler.handleUpstream(SimpleChannelHandler.java:88)
    at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
    at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:559)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
    at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:88)
    at org.jboss.netty.channel.socket.nio.AbstractNioWorker.process(AbstractNioWorker.java:108)
    at org.jboss.netty.channel.socket.nio.AbstractNioSelector.run(AbstractNioSelector.java:337)
    at org.jboss.netty.channel.socket.nio.AbstractNioWorker.run(AbstractNioWorker.java:89)
    at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:178)
    at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108)
    at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Fix Thrift benchmark for Scala 2.11

Currently the Thrift benchmark crashes SBT with the following error on 2.11 (2.10 works just fine):

Annotation generator had thrown the exception.
java.lang.IncompatibleClassChangeError: i.g.f.s.thriftscala.Small and i.g.f.s.thriftscala.Small$Proxy$class disagree on InnerClasses attribute
        at java.lang.Class.getDeclaringClass0(Native Method)
        at java.lang.Class.getDeclaringClass(Class.java:1106)
        at java.lang.Class.getEnclosingClass(Class.java:1142)
        at java.lang.Class.getCanonicalName(Class.java:1231)
        at org.openjdk.jmh.generators.reflection.RFClassInfo.getQualifiedName(RFClassInfo.java:67)
        at org.openjdk.jmh.generators.core.BenchmarkGenerator.buildAnnotatedSet(BenchmarkGenerator.java:213)
        at org.openjdk.jmh.generators.core.BenchmarkGenerator.generate(BenchmarkGenerator.java:108)
        at org.openjdk.jmh.generators.bytecode.JmhBytecodeGenerator.main(JmhBytecodeGenerator.java:100)
        at pl.project13.scala.sbt.SbtJmh$.generateBenchmarkJavaSources(SbtJmh.scala:81)
        at pl.project13.scala.sbt.SbtJmh$$anonfun$jmhSettings$6.apply(SbtJmh.scala:28)
        at pl.project13.scala.sbt.SbtJmh$$anonfun$jmhSettings$6.apply(SbtJmh.scala:28)
        at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
        at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:40)
        at sbt.std.Transform$$anon$4.work(System.scala:63)
        at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:226)
        at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:226)
        at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17)
        at sbt.Execute.work(Execute.scala:235)
        at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:226)
        at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:226)
        at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:159)
        at sbt.CompletionService$$anon$2.call(CompletionService.scala:28)
        at java.util.concurrent.FutureTask.run(FutureTask.java:262)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
        at java.util.concurrent.FutureTask.run(FutureTask.java:262)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:745)

Probably a Scrooge problem, but needs more investigation.

Fix handling of errors when encoding exceptions with scodec

Currently any errors that happen while Scodec is encoding request or response types are correctly wrapped in CodecError (not ServerApplicationError), but if encoding fails while encoding an exception, you'll get the CodecError string wrapped in a ServerApplicationError.

This isn't a terribly big deal, since exceptions should be easy to encode and shouldn't usually fail, but it wouldn't be too hard to fix.

Add JSON over HTTP benchmark

It's not as direct a comparison as thrift-mux, but would help to show where this falls in the spectrum of performance people put up with.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.