Finagle Essentials

@travisbrown

Introduction

Goals

Approach

Side note

Getting started

What you need

Cloning the project

git clone https://github.com/finagle/finagle-example-name-finder.git

Downloading the models (more about this soon)

cd finagle-example-name-finder
sh download-models.sh

Useful links

Build system

Note: we’ll use SBT today

What’s a REPL?

What should it look like?

travis@sidmouth finagle-example-name-finder(master)$ ./sbt
[info] ...
> console
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.10.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_67).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import com.twitter.util.Try
import com.twitter.util.Try

Named-entity recognition

Our example project

What is named-entity recognition?

Kinds of "names" commonly recognized

Example input

On account of the bequest of the late Ezekiah Hopkins, of Lebanon, Pennsylvania, U. S. A., there is now another vacancy open which entitles a member of the League to a salary of £4 a week for purely nominal services.

Example output

On account of the bequest of the late Ezekiah Hopkins, of Lebanon, Pennsylvania, U. S. A., there is now another vacancy open which entitles a member of the League to a salary of £4 a week for purely nominal services.

Pipeline

Named entity recognition pipeline

OpenNLP

Example usage

val sentDetector = new SentenceDetectorME(new SentenceModel(sdStream))
val tokenizer = new TokenizerME(new TokenizerModel(tokStream))
val finder = new NameFinderME(new TokenNameFinderModel(nfStream))

val sentences = sentDetector.sentDetect(document)
val tokenized = sentences map { s => tokenizer.tokenize(s) }
val nameSpans: Seq[String] = tokenized map { tokens =>
  Span.spansToStrings(finder.find(tokens), tokens)
}

finder.clearAdaptiveData()

Limitations of the API in this context

java.lang.IllegalArgumentException: The span [268..276) is outside
  the given text which has length 155!

Goal for our example project

Writing a Scala wrapper

Handling errors with exceptions

def parseAndIncrement(input: String): Int = input.toInt + 1

Modeling failure as a value

def parseAndIncrement(input: String): Try[Int] =
  Try { input.toInt } map { i => i + 1 }

Chaining computations that may fail

def safeDivide(n: Int, d: Int): Try[Int] = Try { n / d }

val good = for {
  n <- parseAndIncrement("5")
  d <- parseAndIncrement("1")
  result <- safeDivide(n, d)
} yield result

val bad1 = for {
  n <- parseAndIncrement("5")
  d <- parseAndIncrement("-1")
  result <- safeDivide(n, d)
} yield result

val bad2 = for {
  n <- parseAndIncrement("v")
  d <- parseAndIncrement("1")
  result <- safeDivide(n, d)
} yield result

Desugaring

val good = for {
  n <- parseAndIncrement("5")
  d <- parseAndIncrement("1")
  result <- safeDivide(n, d)
} yield result

val sugarFreeGood = parseAndIncrement("5").flatMap { n =>
  parseAndIncrement("1").flatMap { d =>
    safeDivide(n, d)
  }
}

Other methods

val tries = Seq("1", "2", "3").map(parseAndIncrement)

Try.collect(tries)

bad2.getOrElse(0)

bad2.rescue {
  case t: NumberFormatException => com.twitter.util.Return(0)
}

Relationship to Option and Either

Try in the Scala standard library

Testing with ScalaTest

Introduction to Finagle

Futures

Like Try, but with an extra state

Futures, try, etc.

Types

Future combinators

Like Try, can be combined using map, flatMap, handle, rescue, etc.

Also allows registration of callbacks:

How futures are executed

Pop quiz

When do these return?

import com.twitter.util.Future

val f1 = Future { Thread.sleep(5000) }
val f2 = Future { 0 }.map { _ => Thread.sleep(5000) }
val f3 = Future.value(Thread.sleep(5000))
val f4 = for {
  a <- Future { 1 }
  b <- Future { Thread.sleep(5000); 2 }
} yield a + b

Extra credit

When do these return?

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

val f1 = Future { Thread.sleep(5000) }
val f2 = Future { 0 }.map { _ => Thread.sleep(5000) }
val f3 = Future.successful(Thread.sleep(5000))
val f4 = for {
  a <- Future { 1 }
  b <- Future { Thread.sleep(5000); 2 }
} yield a + b

From the quickstart

val service = new Service[HttpRequest, HttpResponse] {
  def apply(req: HttpRequest): Future[HttpResponse] =
    Future.value(new DefaultHttpResponse(
      req.getProtocolVersion, HttpResponseStatus.OK))
}

We need to be careful with I/O, since Future.value blocks a Finagle thread

Rule of thumb (courtesy of Moses Nakamura)

Future pools

import com.twitter.util.FuturePool

val pool = FuturePool.unboundedPool

val f1 = pool { Thread.sleep(5000); 0 }
val f2 = pool { 0 }.flatMap { i => pool { expensiveOp(i) }}

Sequencing computations

for {
  foo <- getFoo()
  bar <- getBar(foo)
  baz <- getBaz(foo) // Don't do this!
} yield (bar, baz)

What’s wrong here?

One solution

for {
  foo <- getFoo()
  bar = getBar(foo)
  baz = getBaz(foo)
  barValue <- bar
  bazValue <- baz
} yield (barValue, bazValue)

A better solution

for {
  foo <- getFoo()
  pair <- getBar(foo).join(getBaz(foo))
} yield pair

"Applicative" sequencing

More applicative sequencing

Future.collect(futures: Seq[Future[Int]])

Services

A service is a function

class Service[-Req, +Rep] extends (Req => Future[Rep])

What services aren’t

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

val parserService = new Service[String, Int] {
  def apply(request: String) = Future(request.toInt)
}

Servers

Servers make services available on the network over a protocol

import com.twitter.finagle.Http

val myHttpService: Service[HttpRequest, HttpResponse] = ???

val server = Http.serve(":8080", myHttpService)

Clients

The term "client" is overloaded

Example: HTTP client

A client in our third sense

val client: Service[HttpRequest, HttpResponse] =
  Http.newService("www.google.com:80")

Filters

Filters have a complicated-looking type:

class Filter[-ReqIn, +RepOut, +ReqOut, -RepIn]
  extends (ReqIn, Service[ReqOut, RepIn]) => Future[RepOut]

Filters are actually relatively simple: they’re just service transformers

Timeout filter

TimeoutFilter is an example of a SimpleFilter (doesn’t change types)

import com.twitter.conversions.time._
import com.twitter.finagle.util.DefaultTimer
import com.twitter.finagle.service.TimeoutFilter

val myTimeoutFilter =
  new TimeoutFilter[String, Int](1.second, DefaultTimer.twitter)

Using a timeout filter

import com.twitter.util.FuturePool

val slowParserService = new Service[String, Int] {
  def apply(request: String) = FuturePool.unboundedPool {
    Thread.sleep(5000); request.toInt
  }
}

val myService = myTimeoutFilter andThen slowParserService

Protocols

Finagle is designed to make it possible to define many components in a protocol-agnostic fashion

Thrift and Scrooge

Where’s the code?

Implementing the Scrooge interfaces

We need to define a method implementation for every function in our service

Putting it all together

A bad solution

What’s wrong with NaiveNameRecognizerService?

(Hint: there’s more than one thing)

A better solution

twitter-server

twitter-server: an alternative App

Using twitter-server

./sbt run

Then visit the admin page, metrics, etc. on port 9990

/

#