Skip to content

Finch 0.12

Compare
Choose a tag to compare
@vkostyukov vkostyukov released this 18 Jan 03:10
· 1136 commits to master since this release

Scala 2.12

This release finally brings Scala 2.12 support (see #668, thanks @ilya-murzinov, @travisbrown, @clhodapp). As for 0.12, Finch is cross-published for both 2.11 and 2.12.

String-less Encoding with Circe/Jackson

Since 0.11, Finch parses JSON directly from bytes (not strings), which improved its throughput quite dramatically (see #671). This time we're moving towards "string-less encoding" (see #676) with Circe and Jackson by printing directly into a byte buffer and returning that as an HTTP payload (see #717 and #714, thanks @imliar, @travisbrown).

The benchmark numbers look inspiring and demonstrate quite well how the initial idea of hiding the serialization/deserialization logic behind functional abstractions is starting paying back. Being able to control this part of request lifecycle allows for optimizations specific to a concrete JSON library.

Overall, the numbers confirm that printing directly to bytes cuts allocations in half (and improves the throughput).

circe-core (string)

[info] Benchmark                                               Mode  Cnt        Score        Error   Units
[info] ToServiceBenchmark.foos                                thrpt   20      463.981 ±     24.913   ops/s
[info] ToServiceBenchmark.foos:·gc.alloc.rate.norm            thrpt   20  5002135.084 ±   3252.704    B/op
[info] ToServiceBenchmark.ints                                thrpt   20     2118.919 ±     49.846   ops/s
[info] ToServiceBenchmark.ints:·gc.alloc.rate.norm            thrpt   20   873272.466 ±    106.930    B/op

circe-core (bytes)

[info] Benchmark                                               Mode  Cnt        Score        Error   Units
[info] ToServiceBenchmark.foos                                thrpt   20      523.730 ±     21.871   ops/s
[info] ToServiceBenchmark.foos:·gc.alloc.rate.norm            thrpt   20  2602833.269 ±     76.806    B/op
[info] ToServiceBenchmark.ints                                thrpt   20     2234.631 ±    111.156   ops/s
[info] ToServiceBenchmark.ints:·gc.alloc.rate.norm            thrpt   20   545837.968 ±  43806.910    B/op

EndpointResult

Previously, Endpoint result was modeled as Option[Tuple2[Input, Rerunnable[Output[_]]]] indicating that result could be either skipped (i.e., None) or matched and both remainder and the output is returned. This release introduces a new type EndpointResult (see #707) that encodes this very behavior as an ADT with two cases Matched and Skipped.

A standalone abstraction not only simplifies the reasoning about endpoints but also improves their performance. Technically, EndpointResult is a flattened version of Option[Tuple2[_, _]] so instead of two nested objects we only need one to carry the result. This simple idea impacts nearly all endpoint operations slightly reducing allocations and improving throughput (up to 5% better on some benchmarks).

Here are the numbers from map* variants (before and after):

[info] MapBenchmark.mapAsync                               avgt    6   429.113 ±   43.297   ns/op
[info] MapBenchmark.mapAsync:·gc.alloc.rate.norm           avgt    6   776.000 ±    0.001    B/op
[info] MapBenchmark.mapAsync                               avgt    6   407.126 ±   12.807   ns/op
[info] MapBenchmark.mapAsync:·gc.alloc.rate.norm           avgt    6   720.000 ±    0.001    B/op

[info] MapBenchmark.mapOutputAsync                         avgt    6   821.786 ±   52.045   ns/op
[info] MapBenchmark.mapOutputAsync:·gc.alloc.rate.norm     avgt    6  1376.001 ±    0.001    B/op
[info] MapBenchmark.mapOutputAsync                         avgt    6   777.654 ±   26.444   ns/op
[info] MapBenchmark.mapOutputAsync:·gc.alloc.rate.norm     avgt    6  1320.001 ±    0.001    B/op

Better Testing APIs

Methods for querying a rerunnable Output returned from an Endpoint have been renamed to be explicitly alerting about their blocking nature. Now they all prefixed with await and take an optional Duration indicating the upper bound of await time (previously 10 seconds).

Deprecation schema:

  • value -> awaitValueUnsafe()
  • tryValue -> awaitValue()
  • output -> awaitOutputUnsafe()
  • tryOutput -> awaitOutput()

Basic HTTP Auth

Finch's implementation of Basic HTTP Auth has been moved to its own project finagle-http-auth and promoted to Finagle filter so it could be used with bare metal finagle-http.

To enable the Basic HTTP Auth on Finch's endpoint, apply the BasicAuth.Server filter to the service yield from toService call.

scala> import com.twitter.finagle.http.BasicAuth, io.finch._

scala> val ba = BasicAuth.serverFromCredentials("admin", "12345")
ba: com.twitter.finagle.http.BasicAuth.Server = <function2>

scala> val s = ba.andThen(Endpoint.const("foo").toServiceAs[Text.Plain])
s: com.twitter.finagle.Service[Request, Response]

Other Changes

  • New method Endpoint.liftToTry that works in a similar fashion to Future.liftToTry (see #710)
  • New method Endpoint.productWith that also accepts a function (A, B) => C (see #692, thanks @imliar)
  • Jackson's ObjectMapper is now embedded in io.finch.jackson. No need for an implicit instance anymore (see #714)

Dependencies

  • Finagle 6.41
  • Circe 0.7
  • Cats 0.9
  • Shapeless 2.3.2