Permalink
Browse files

scrooge: Generate a finagle Service per thrift method

Problem

Scrooge-generated services are not finagle Services, so they do not allow using Filters.

Solution

Generate a wrapper service that delegates to the underlying thrift service.

Result

For a thrift service, e.g.

service Logger {
  string log(1: string message, 2: i32 logLevel);
  i32 getLogSize();
}

Scrooge generates:

class Logger$ServiceImpl(underlying: Logger[Future]) {
  val log: com.twitter.finagle.Service[(String, Int), String] = ...
  val getLogSize: com.twitter.finagle.Service[Unit, Int] = ...
}

Usage in finagle:

val client = ThriftMux.newServiceIface(Logger, "localhost:8080")

client.log(Logger.Log.Args("message", 1)) onSuccess {...}

This avoids the reflection-based initialization (ThriftRichClient) by collecting the relevant types during generation.

Compatibility constructor to build a FutureIface from a ServiceIface:

val loggerFutureIface = Logger.newFutureIface(loggerServiceIface)
loggerFutureIface.log("msg")

Client configuration is done by e.g.

ThriftMux.client.withClientId(ClientId("asdf")).newServiceIface(Logger, dest)

RB_ID=663690
  • Loading branch information...
nshkrob committed Jul 23, 2015
1 parent 8554aa1 commit 3fbb635936d82c00456b332647ea9136841f3227
Showing with 663 additions and 316 deletions.
  1. +2 −0 CHANGES
  2. +40 −0 scrooge-core/src/main/scala/com/twitter/scrooge/ThriftStruct.scala
  3. +4 −2 scrooge-generator/src/main/resources/javagen/finagleClient.java
  4. +6 −3 scrooge-generator/src/main/resources/javagen/finagleClientFunction.java
  5. +3 −3 scrooge-generator/src/main/resources/javagen/finagleServiceFunction.java
  6. +1 −1 scrooge-generator/src/main/resources/javagen/function.java
  7. +9 −4 scrooge-generator/src/main/resources/javagen/service.java
  8. +7 −6 scrooge-generator/src/main/resources/scalagen/finagleClient.scala
  9. +5 −3 scrooge-generator/src/main/resources/scalagen/finagleClientFunction.scala
  10. +1 −1 scrooge-generator/src/main/resources/scalagen/finagleService.scala
  11. +3 −3 scrooge-generator/src/main/resources/scalagen/finagleServiceFunction.scala
  12. +1 −4 scrooge-generator/src/main/resources/scalagen/function.scala
  13. +71 −12 scrooge-generator/src/main/resources/scalagen/service.scala
  14. +11 −6 scrooge-generator/src/main/resources/scalagen/struct.scala
  15. +4 −1 scrooge-generator/src/main/scala/com/twitter/scrooge/AST/Definition.scala
  16. +1 −1 scrooge-generator/src/main/scala/com/twitter/scrooge/android_generator/AndroidGenerator.scala
  17. +8 −36 scrooge-generator/src/main/scala/com/twitter/scrooge/backend/CocoaGenerator.scala
  18. +2 −2 scrooge-generator/src/main/scala/com/twitter/scrooge/backend/ConstsTemplate.scala
  19. +5 −5 scrooge-generator/src/main/scala/com/twitter/scrooge/backend/EnumTemplate.scala
  20. +81 −23 scrooge-generator/src/main/scala/com/twitter/scrooge/backend/Generator.scala
  21. +12 −40 scrooge-generator/src/main/scala/com/twitter/scrooge/backend/JavaGenerator.scala
  22. +11 −46 scrooge-generator/src/main/scala/com/twitter/scrooge/backend/ScalaGenerator.scala
  23. +132 −56 scrooge-generator/src/main/scala/com/twitter/scrooge/backend/ServiceTemplate.scala
  24. +46 −36 scrooge-generator/src/main/scala/com/twitter/scrooge/backend/StructTemplate.scala
  25. +1 −1 scrooge-generator/src/main/scala/com/twitter/scrooge/mustache/Dictionary.scala
  26. +1 −1 scrooge-generator/src/test/scala/com/twitter/scrooge/android_generator/AndroidGeneratorSpec.scala
  27. +195 −20 scrooge-generator/src/test/scala/com/twitter/scrooge/backend/ServiceGeneratorSpec.scala
View
@@ -5,6 +5,8 @@ as it is included in Scrooge's user's guide.
3.x
~~~~~~~
- scrooge: Generate a finagle Service per thrift method (Service interface)
3.19.0
~~~~~~~
- scrooge: Performance improvements and bug fixes.
@@ -7,6 +7,22 @@ trait ThriftStruct {
def write(oprot: TProtocol)
}
trait ThriftResponse[Result] {
def successField: Option[Result]
def exceptionFields: Iterable[Option[ThriftException]]
/**
* Return the first nonempty exception field.
*/
def firstException(): Option[ThriftException] =
exceptionFields.collectFirst(ThriftResponse.exceptionIsDefined)
}
object ThriftResponse {
private val exceptionIsDefined: PartialFunction[Option[ThriftException], ThriftException] = {
case Some(exception) => exception
}
}
/**
* Unions are tagged with this trait as well as with [[ThriftStruct]].
*/
@@ -64,3 +80,27 @@ abstract class ThriftStructCodec3[T <: ThriftStruct] extends ThriftStructCodec[T
}
}
/**
* Metadata for a thrift method.
*/
trait ThriftMethod {
/** A struct wrapping method arguments */
type Args <: ThriftStruct
/** The successful return type */
type SuccessType
/** Contains success or thrift application exceptions */
type Result <: ThriftResponse[SuccessType] with ThriftStruct
/** Thrift method name */
def name: String
/** Thrift service name. A thrift service is a list of methods. */
def serviceName: String
/** Codec for the request args */
def argsCodec: ThriftStructCodec3[Args]
/** Codec for the response */
def responseCodec: ThriftStructCodec3[Result]
/** True for oneway thrift methods */
def oneway: Boolean
}
@@ -120,6 +120,8 @@ public __Stats(String name) {
{{/hasParent}}
{{#functions}}
{{>function}}
{{/function}}
{{#functionInfo}}
{{>finagleClientFunction}}
{{/functionInfo}}
{{/functions}}
}
@@ -8,13 +8,15 @@
return _{{__stats_name}};
}
{{#headerInfo}}{{>header}}{{/headerInfo}} {
{{#functionInfo}}{{>header}} {
{{__stats_name}}().requestsCounter.incr();
Future<{{type}}> rv = this.service.apply(encodeRequest("{{clientFuncNameForWire}}", new {{ArgsStruct}}({{argNames}}))).flatMap(new Function<byte[], Future<{{type}}>>() {
Future<{{type}}> rv = this.service.apply(encodeRequest("{{clientFuncNameForWire}}", new {{funcObjectName}}.Args({{argNames}}))).flatMap(new Function<byte[], Future<{{type}}>>() {
public Future<{{type}}> apply(byte[] in) {
try {
{{ResultStruct}} result = decodeResponse(in, {{ResultStruct}}.CODEC);
{{funcObjectName}}.Result result = decodeResponse(in, {{funcObjectName}}.Result.CODEC);
{{#hasThrows}}
Exception exception = null;
@@ -60,3 +62,4 @@ public void onFailure(Throwable t) {
return rv;
}
{{/functionInfo}}
@@ -1,7 +1,7 @@
addFunction("{{serviceFuncNameForWire}}", new Function2<TProtocol, Integer, Future<byte[]>>() {
public Future<byte[]> apply(TProtocol iprot, final Integer seqid) {
try {
{{ArgsStruct}} args = {{ArgsStruct}}.decode(iprot);
{{funcObjectName}}.Args args = {{funcObjectName}}.Args.decode(iprot);
iprot.readMessageEnd();
Future<{{typeName}}> result;
try {
@@ -11,13 +11,13 @@
}
return result.flatMap(new Function<{{typeName}}, Future<byte[]>>() {
public Future<byte[]> apply({{typeName}} value){
return reply("{{serviceFuncNameForWire}}", seqid, new {{ResultStruct}}.Builder(){{^isVoid}}.success(value){{/isVoid}}.build());
return reply("{{serviceFuncNameForWire}}", seqid, new {{funcObjectName}}.Result.Builder(){{^isVoid}}.success(value){{/isVoid}}.build());
}
}).rescue(new Function<Throwable, Future<byte[]>>() {
public Future<byte[]> apply(Throwable t) {
{{#exceptions}}
if (t instanceof {{exceptionType}}) {
return reply("{{ServiceName}}", seqid, new {{ResultStruct}}.Builder().{{fieldName}}(({{exceptionType}}) t).build());
return reply("{{ServiceName}}", seqid, new {{funcObjectName}}.Result.Builder().{{fieldName}}(({{exceptionType}}) t).build());
}
{{/exceptions}}
return Future.exception(t);
@@ -1,2 +1,2 @@
{{docstring}}
public {{#generic}}{{generic}}<{{/generic}}{{typeName}}{{#generic}}>{{/generic}} {{funcName}}({{fieldParams}}){{#hasThrows}} throws {{#throws}}{{typeName}}{{/throws|, }}{{/hasThrows}}
public {{#generic}}{{generic}}<{{/generic}}{{typeName}}{{#generic}}>{{/generic}} {{funcName}}({{fieldParams}}){{#hasThrows}} throws {{#throws}}{{throwType}}{{/throws|, }}{{/hasThrows}}
@@ -71,14 +71,19 @@ public FinagledService (
{{/withFinagle}}
{{#internalStructs}}
{{#internalArgsStruct}}
{{#thriftFunctions}}
public static class {{funcObjectName}} {
{{#functionArgsStruct}}
{{>struct}}
{{/internalArgsStruct}}
{{/functionArgsStruct}}
{{#internalResultStruct}}
{{>struct}}
{{/internalResultStruct}}
{{/internalStructs}}
{{#functionResultStruct}}
{{>struct}}
{{/functionResultStruct}}
}
{{/thriftFunctions}}
{{#finagleClients}}
{{>finagleClient}}
{{/finagleClients}}
@@ -1,6 +1,6 @@
package {{package}}
import com.twitter.finagle.{SourcedException, Service => FinagleService}
import com.twitter.finagle.{SourcedException, Service}
import com.twitter.finagle.stats.{NullStatsReceiver, StatsReceiver}
import com.twitter.finagle.thrift.{Protocols, ThriftClientRequest}
import com.twitter.scrooge.{ThriftStruct, ThriftStructCodec}
@@ -17,10 +17,10 @@ import scala.language.higherKinds
{{docstring}}
@javax.annotation.Generated(value = Array("com.twitter.scrooge.Compiler"))
class {{ServiceName}}$FinagleClient(
{{#hasParent}}override {{/hasParent}}val service: FinagleService[ThriftClientRequest, Array[Byte]],
{{#hasParent}}override {{/hasParent}}val protocolFactory: TProtocolFactory = Protocols.binaryFactory(),
{{#hasParent}}override {{/hasParent}}val serviceName: String = "{{ServiceName}}",
stats: StatsReceiver = NullStatsReceiver
{{#hasParent}}override {{/hasParent}}val service: Service[ThriftClientRequest, Array[Byte]],
{{#hasParent}}override {{/hasParent}}val protocolFactory: TProtocolFactory = Protocols.binaryFactory(),
{{#hasParent}}override {{/hasParent}}val serviceName: String = "{{ServiceName}}",
stats: StatsReceiver = NullStatsReceiver
) extends {{#hasParent}}{{finagleClientParent}}(service, protocolFactory, serviceName, stats) with {{/hasParent}}{{ServiceName}}[Future] {
import {{ServiceName}}._
{{^hasParent}}
@@ -80,6 +80,7 @@ class {{ServiceName}}$FinagleClient(
{{/hasParent}}
private[this] val scopedStats = if (serviceName != "") stats.scope(serviceName) else stats
{{#functions}}
{{>function}}
{{>finagleClientFunction}}
{{/function}}
}
@@ -4,10 +4,11 @@ private[this] object {{__stats_name}} {
val FailuresCounter = scopedStats.scope("{{clientFuncNameForWire}}").counter("failures")
val FailuresScope = scopedStats.scope("{{clientFuncNameForWire}}").scope("failures")
}
{{#headerInfo}}{{>header}}{{/headerInfo}} = {
{{#functionInfo}}
{{>header}} = {
{{__stats_name}}.RequestsCounter.incr()
this.service(encodeRequest("{{clientFuncNameForWire}}", {{ArgsStruct}}({{argNames}}))) flatMap { response =>
val result = decodeResponse(response, {{ResultStruct}})
this.service(encodeRequest("{{clientFuncNameForWire}}", {{funcObjectName}}.Args({{argNames}}))) flatMap { response =>
val result = decodeResponse(response, {{funcObjectName}}.Result)
val exception: Future[Nothing] =
{{#hasThrows}}
if (false)
@@ -43,3 +44,4 @@ private[this] object {{__stats_name}} {
{{__stats_name}}.FailuresScope.counter(Throwables.mkString(ex): _*).incr()
}
}
{{/functionInfo}}
@@ -1,6 +1,6 @@
package {{package}}
import com.twitter.finagle.{Service => FinagleService, Thrift}
import com.twitter.finagle.{Service, Thrift}
import com.twitter.finagle.stats.{NullStatsReceiver, StatsReceiver}
import com.twitter.scrooge.{ThriftStruct, TReusableMemoryTransport}
import com.twitter.util.Future
@@ -1,17 +1,17 @@
addFunction("{{serviceFuncNameForWire}}", { (iprot: TProtocol, seqid: Int) =>
try {
val args = {{ArgsStruct}}.decode(iprot)
val args = {{funcObjectName}}.Args.decode(iprot)
iprot.readMessageEnd()
(try {
iface.{{serviceFuncNameForCompile}}({{argNames}})
} catch {
case e: Exception => Future.exception(e)
}) flatMap { value: {{typeName}} =>
reply("{{serviceFuncNameForWire}}", seqid, {{ResultStruct}}({{resultNamedArg}}))
reply("{{serviceFuncNameForWire}}", seqid, {{funcObjectName}}.Result({{resultNamedArg}}))
} rescue {
{{#exceptions}}
case e: {{exceptionType}} => {
reply("{{serviceFuncNameForWire}}", seqid, {{ResultStruct}}({{fieldName}} = _root_.scala.Some(e)))
reply("{{serviceFuncNameForWire}}", seqid, {{funcObjectName}}.Result({{fieldName}} = Some(e)))
}
{{/exceptions}}
case e => Future.exception(e)
@@ -1,5 +1,2 @@
{{docstring}}
{{#throws}}
@throws(classOf[{{typeName}}])
{{/throws}}
def {{funcName}}({{fieldParams}}): {{#generic}}{{generic}}[{{/generic}}{{typeName}}{{#generic}}]{{/generic}}
def {{funcName}}({{fieldParams}}): {{#generic}}{{generic}}[{{/generic}}{{typeName}}{{#generic}}]{{/generic}}
@@ -1,10 +1,15 @@
package {{package}}
import com.twitter.scrooge
import com.twitter.scrooge.{
LazyTProtocol,
TFieldBlob, ThriftService, ThriftStruct,
ThriftStructCodec, ThriftStructCodec3,
ThriftStructFieldInfo, ThriftUtil}
{{#withFinagle}}
import com.twitter.finagle.thrift.{Protocols, ThriftClientRequest, ThriftServiceIface}
import com.twitter.util.Future
{{/withFinagle}}
import java.nio.ByteBuffer
import java.util.Arrays
import org.apache.thrift.protocol._
@@ -17,7 +22,6 @@ import scala.collection.mutable.{
ArrayBuffer => mutable$ArrayBuffer, Buffer => mutable$Buffer,
HashMap => mutable$HashMap, HashSet => mutable$HashSet}
import scala.collection.{Map, Set}
import scala.language.higherKinds
{{docstring}}
@@ -28,29 +32,84 @@ trait {{ServiceName}}[+MM[_]] {{#genericParent}}extends {{genericParent}} {{/gen
{{/genericFunctions}}
}
{{docstring}}
object {{ServiceName}} {
{{#internalStructs}}
{{#internalArgsStruct}}
{{>struct}}
{{/internalArgsStruct}}
{{#withFinagle}}
case class ServiceIface(
{{#inheritedFunctions}}
{{funcName}}: com.twitter.finagle.Service[{{ParentServiceName}}.{{funcObjectName}}.Args, {{ParentServiceName}}.{{funcObjectName}}.Result]
{{/inheritedFunctions|,}}
) extends {{#parent}}{{parent}}.__ServiceIface
with {{/parent}}__ServiceIface
// This is needed to support service inheritance.
trait __ServiceIface {{#parent}} extends {{parent}}.__ServiceIface {{/parent}} {
{{#asyncFunctions}}
def {{funcName}}: com.twitter.finagle.Service[{{funcObjectName}}.Args, {{funcObjectName}}.Result]
{{/asyncFunctions}}
}
implicit object ServiceIfaceBuilder
extends com.twitter.finagle.thrift.ServiceIfaceBuilder[ServiceIface] {
def newServiceIface(
binaryService: com.twitter.finagle.Service[ThriftClientRequest, Array[Byte]],
pf: TProtocolFactory = Protocols.binaryFactory(),
stats: com.twitter.finagle.stats.StatsReceiver
): ServiceIface =
new ServiceIface(
{{#inheritedFunctions}}
{{funcName}} = ThriftServiceIface({{ParentServiceName}}.{{funcObjectName}}, binaryService, pf, stats)
{{/inheritedFunctions|,}}
)
}
class MethodIface(serviceIface: __ServiceIface)
extends {{#parent}}{{parent}}.MethodIface(serviceIface) with {{/parent}}{{ServiceName}}[Future] {
{{#thriftFunctions}}
private[this] val __{{funcName}}_service =
ThriftServiceIface.resultFilter({{ServiceName}}.{{funcObjectName}}) andThen serviceIface.{{funcName}}
def {{funcName}}({{fieldParams}}): Future[{{typeName}}] =
__{{funcName}}_service({{ServiceName}}.{{funcObjectName}}.Args({{argNames}})){{^isVoid}}{{/isVoid}}{{#isVoid}}.unit{{/isVoid}}
{{/thriftFunctions}}
}
implicit object MethodIfaceBuilder
extends com.twitter.finagle.thrift.MethodIfaceBuilder[ServiceIface, {{ServiceName}}[Future]] {
def newMethodIface(serviceIface: ServiceIface): {{ServiceName}}[Future] =
new MethodIface(serviceIface)
}
{{/withFinagle}}
{{#thriftFunctions}}
object {{funcObjectName}} extends scrooge.ThriftMethod {
{{#functionArgsStruct}}
{{>struct}}
{{/functionArgsStruct}}
type SuccessType = {{typeName}}
{{#internalResultStruct}}
{{>struct}}
{{>struct}}
{{/internalResultStruct}}
{{/internalStructs}}
{{#withFinagle}}
import com.twitter.finagle.thrift.Protocols
import com.twitter.util.Future
val name = "{{funcName}}"
val serviceName = "{{ServiceName}}"
val argsCodec = Args
val responseCodec = Result
val oneway = {{is_oneway}}
}
trait FutureIface extends {{#futureIfaceParent}}{{futureIfaceParent}} with{{/futureIfaceParent}} {{ServiceName}}[Future] {
{{/thriftFunctions}}
{{#withFinagle}}
trait FutureIface extends {{#futureIfaceParent}}{{futureIfaceParent}} with {{/futureIfaceParent}}{{ServiceName}}[Future] {
{{#asyncFunctions}}
{{>function}}
{{/asyncFunctions}}
}
class FinagledClient(
service: com.twitter.finagle.Service[com.twitter.finagle.thrift.ThriftClientRequest, Array[Byte]],
service: com.twitter.finagle.Service[ThriftClientRequest, Array[Byte]],
protocolFactory: TProtocolFactory = Protocols.binaryFactory(),
serviceName: String = "{{ServiceName}}",
stats: com.twitter.finagle.stats.StatsReceiver = com.twitter.finagle.stats.NullStatsReceiver)
@@ -404,16 +404,16 @@ class {{StructName}}(
{
import {{StructName}}._
{{^withTrait}}
def this(
def this(
{{#fields}}
{{fieldName}}: {{>optionalType}}{{#hasDefaultValue}} = {{defaultFieldValue}}{{/hasDefaultValue}}{{#optional}} = _root_.scala.None{{/optional}}
{{fieldName}}: {{>optionalType}}{{#hasDefaultValue}} = {{defaultFieldValue}}{{/hasDefaultValue}}{{#optional}} = _root_.scala.None{{/optional}}
{{/fields|,}}
) = this(
) = this(
{{#fields}}
{{fieldName}},
{{fieldName}},
{{/fields}}
Map.empty
)
Map.empty
)
{{/withTrait}}
{{#withTrait}}
@@ -428,6 +428,11 @@ class {{StructName}}(
def _{{indexP1}} = {{fieldName}}
{{/fields}}
{{#isResponse}}
def successField: Option[{{successFieldType}}] = {{successFieldValue}}
def exceptionFields: Iterable[Option[com.twitter.scrooge.ThriftException]] = {{exceptionValues}}
{{/isResponse}}
{{#withFieldGettersAndSetters}}
/**
* Gets a field value encoded as a binary blob using TCompactProtocol. If the specified field
Oops, something went wrong.

0 comments on commit 3fbb635

Please sign in to comment.