Skip to content

Commit

Permalink
inject-thrift-client: Support Scrooge "service-per-endpoint" function…
Browse files Browse the repository at this point in the history
…ality

Problem
Scrooge 4 was just released, and inject-thrift-client has not yet been upgraded.

Solution
Upgrade inject-thrift-client and create a new FilteredThriftClientModule class.

RB_ID=739672
  • Loading branch information
scosenza authored and jenkins committed Oct 1, 2015
1 parent 7206740 commit 91d9221
Show file tree
Hide file tree
Showing 29 changed files with 882 additions and 91 deletions.
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ lazy val versions = new {
val mustache = "0.8.18"
val nscalaTime = "1.6.0"
val servletApi = "2.5"
val scrooge = "4.0.0"
val scrooge = "4.1.0"
val slf4j = "1.7.7"
val twitterServer = "1.14.0"
val util = "6.28.0"
Expand Down Expand Up @@ -234,7 +234,7 @@ lazy val injectRequestScope = (project in file("inject/inject-request-scope")).
)

lazy val injectThriftClient = (project in file("inject/inject-thrift-client")).
settings((ScroogeSBT.newSettings ++ injectBuildSettings): _*).
settings(injectBuildSettings).
settings(
name := "inject-thrift-client",
moduleName := "inject-thrift-client",
Expand Down
70 changes: 70 additions & 0 deletions inject/inject-thrift-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Summary
inject-thrift-client is a library for configuring injectable thrift clients. The examples below demonstrate creating a thrift-client for the following thrift defined service:
```thrift
service Greeter {
string hi(
1: string name
) throws (1:InvalidOperation invalidOperation)
ByeResponse bye(
1: string name
2: i32 age
) throws (1:ByeOperation byeOperation)
}
```

## FilteredThriftClientModule Usage
FilteredThriftClientModule integrates with Scrooge 4 ["services-per-endpoint"](https://finagle.github.io/blog/2015/09/10/services-per-endpoint-in-scrooge/), supporting per-method filter chains which can retry on requests, successful responses, failed futures, and thrift-idl defined exceptions.

```scala
object GreeterThriftClientModule
extends FilteredThriftClientModule[Greeter[Future], Greeter.ServiceIface] {

override val label = "greeter-thrift-client"
override val dest = "flag!greeter-thrift-service"
override val connectTimeout = 1.minute.toDuration

override def createFilteredClient(
serviceIface: Greeter.ServiceIface,
filterBuilder: FilterBuilder): Greeter[Future] = {

Thrift.newMethodIface(serviceIface.copy(
hi = filterBuilder.method(Hi)
.timeout(2.minutes)
.constantRetry(
requestTimeout = 1.minute,
shouldRetryResponse = {
case Return(Hi.Result(_, Some(e: InvalidOperation))) => true
case Return(Hi.Result(Some(success), _)) => success == "ERROR"
case Throw(NonFatal(_)) => true
},
start = 50.millis,
retries = 3)
.filter[HiLoggingThriftClientFilter]
.andThen(serviceIface.hi),
bye = filterBuilder.method(Bye)
.exponentialRetry(
shouldRetryResponse = NonFatalExceptions,
requestTimeout = 1.minute,
start = 50.millis,
multiplier = 2,
retries = 3)
.andThen(serviceIface.bye)))
}
}
```

Then add GreeterThriftClientModule to your list of server modules, and inject the "Greeter" thrift-client as such:
```scala
class MyClass @Inject()(
greeter: Greeter[Future])
```

## ThriftClientModule Usage
If you don't need client retries or other client filters, ThriftClientModule can be used to simply create an injectable thrift client as such:
```scala
object GreeterThriftClientModule extends ThriftClientModule[Greeter[Future]] {
override val label = "greeter-thrift-client"
override val dest = "flag!greeter-thrift-service"
}
```
11 changes: 11 additions & 0 deletions inject/inject-thrift-client/src/main/java/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
java_library(name='java',
provides = artifact(
org = 'com.twitter.inject',
name = 'inject-thrift-client-java',
repo = artifactory,
),
dependencies=[
'3rdparty/jvm/com/google/inject:guice',
],
sources=rglobs('*.java'),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.twitter.inject.thrift;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import com.google.inject.BindingAnnotation;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@BindingAnnotation
public @interface NonFiltered {
}
4 changes: 3 additions & 1 deletion inject/inject-thrift-client/src/main/scala/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ scala_library(name='scala',
'finatra/inject/inject-core',
'scrooge/scrooge-core',
'util/util-core',
'util/util-stats',
],
sources=rglobs('*.scala'),
java_sources=[
'finatra/inject/inject-thrift-client/src/main/java'
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.twitter.inject.thrift

import com.twitter.finagle.stats.StatsReceiver
import com.twitter.inject.Injector
import com.twitter.scrooge.ThriftMethod
import javax.inject.Inject

class FilterBuilder @Inject()(
injector: Injector,
statsReceiver: StatsReceiver) {

def method(method: ThriftMethod): ThriftClientFilterChain[method.Args, method.Result] = {
new ThriftClientFilterChain[method.Args, method.Result](injector, statsReceiver, method)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.twitter.inject.thrift

import com.github.nscala_time.time
import com.google.inject.Provides
import com.twitter.finagle._
import com.twitter.finagle.factory.TimeoutFactory
import com.twitter.finagle.param.{Label, Stats}
import com.twitter.finagle.service.TimeoutFilter
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.finagle.thrift.{ClientId, ServiceIfaceBuilder}
import com.twitter.inject.TwitterModule
import com.twitter.inject.thrift.FilteredThriftClientModule.MaxDuration
import com.twitter.inject.thrift.conversions.DurationConversions
import com.twitter.scrooge.{ThriftResponse, ThriftService}
import com.twitter.util.{NonFatal, Return, Throw, Try}
import javax.inject.Singleton
import org.joda.time.Duration
import scala.reflect.ClassTag

object FilteredThriftClientModule {
val MaxDuration = Duration.millis(Long.MaxValue)
}

abstract class FilteredThriftClientModule[FutureIface <: ThriftService : ClassTag, ServiceIface: ClassTag](
implicit builder: ServiceIfaceBuilder[ServiceIface])
extends TwitterModule
with DurationConversions
with time.Implicits {

/**
* Name of client for use in metrics
*/
val label: String

/**
* Destination of client (usually a wily path)
*/
val dest: String

/**
* Enable thrift mux for this connection.
*
* Note: Both server and client must have mux enabled otherwise
* a non-descript ChannelClosedException will be seen.
*/
val mux: Boolean = true

def requestTimeout: Duration = MaxDuration

def connectTimeout: Duration = MaxDuration

def params = {
Stack.Params.empty +
TimeoutFilter.Param(requestTimeout.toTwitterDuration) +
TimeoutFactory.Param(connectTimeout.toTwitterDuration) +
Label(label)
}

/**
* Add filters to the ServiceIface based client
*/
def createFilteredClient(
serviceIface: ServiceIface,
filters: FilterBuilder): FutureIface

@Provides
@Singleton
def providesClient(
@NonFiltered serviceIface: ServiceIface,
filter: FilterBuilder,
statsReceiver: StatsReceiver): FutureIface = {

createFilteredClient(
serviceIface = serviceIface,
filters = filter)
}

@Provides
@NonFiltered
@Singleton
def providesUnfilteredServiceIface(clientId: ClientId, statsReceiver: StatsReceiver): ServiceIface = {
val updatedParams = params +
Stats(statsReceiver.scope("clnt")) +
Thrift.param.ClientId(Some(clientId))

if (mux)
ThriftMux.client.
withParams(updatedParams).
newServiceIface[ServiceIface](dest)
else
Thrift.client.
withParams(updatedParams).
newServiceIface[ServiceIface](dest)
}

/* Common Retry Functions */

lazy val NonFatalExceptions: PartialFunction[Try[ThriftResponse[_]], Boolean] = {
case Throw(NonFatal(_)) => true
case Return(result) => result.firstException exists NonFatal.isNonFatal
}
}
Loading

0 comments on commit 91d9221

Please sign in to comment.