We’ve released Service-per-endpoint support for Scala Thrift clients generated by Scrooge. This change has been introduced in Scrooge 4.0.0.
This allows the use of Finagle filters with Thrift services to do retries, timeouts, etc. in a Finagle-idiomatic way. Scrooge generates a Service
for each Thrift method, so different filter chains can be built for different methods. Typical examples of this are per-method timeout and retry policies.
Here’s a Thrift service definition:
service LoggerService {
string log(1: string message, 2: i32 logLevel);
i32 getLogSize();
}
Before this patch, Scrooge would generate this method-based interface:
trait FutureIface extends LoggerService[Future] {
def log(message: String, logLevel: Int): Future[String]
def getLogSize(): Future[Int]
}
With the patch, Scrooge also generates the following:
case class ServiceIface(
log: Service[Log.Args, Log.Result],
getLogSize: Service[GetLogSize.Args, GetLogSize.Result])
Note that every method in the IDL becomes a Service
for the corresponding Args
and Result
structures. The wrappers are needed to wrap multiple method arguments into one type.
To construct a client, we use newServiceIFace
:
val clientServiceIface: LoggerService.ServiceIface =
Thrift.newServiceIface[LoggerService.ServiceIface]("localhost:1234")
Because every Thrift method is a Finagle Service
, you can decorate them with Filter
s:
val uppercaseFilter = new SimpleFilter[Log.Args, Log.Result] {
def apply(req: Log.Args, service: Service[Log.Args, Log.Result]): Future[Log.Result] = {
val uppercaseRequest = req.copy(message = req.message.toUpperCase)
service(uppercaseRequest)
}
}
def timeoutFilter[Req, Rep](duration: Duration) = {
val exc = new IndividualRequestTimeoutException(duration)
val timer = DefaultTimer.twitter
new TimeoutFilter[Req, Rep](duration, exc, timer)
}
val filteredLog = timeoutFilter(2.seconds) andThen
uppercaseFilter andThen
clientServiceIface.log
val result: Future[Log.Result] = filteredLog(Log.Args("hello", 2))
Sometimes different Thrift methods have different latencies; for example the log
method typically completes in two second, and the getLogSize
method completes in 500 milliseconds. Then we can use a shorter timeout duration for getLogSize
:
val filteredGetLogSize = timeoutFilter(500.milliseconds) andThen
clientServiceIface.getLogSize
val size: Future[GetLogSize.Result] = filteredGetLogSize(GetLogSize.Args())
Existing code works without changes. We don’t plan to deprecate or remove the method-based interfaces.
Additionally, there is a bridge constructor to wrap the old-style method interface around the new service interface.
val filteredMethodIface: LoggerService[Future] =
Thrift.newMethodIface(clientServiceIface.copy(log = filteredLog))
Await.result(filteredMethodIface.log("ping", 3).map(println))
This can be used to insert filters into existing Thrift clients.
Please contact the Finaglers mailing list with questions or bug reports, and if you’re interested in working on projects like Scrooge, get in touch—the Core System Libraries team at Twitter is hiring.