Finagle Integration¶
You can generate Finagle binding code by passing the –finagle option to scrooge. For each Thrift service, Scrooge will generate classes that build Finagle Services for both client and server.
The following examples are for Scala generated code and use the following Thrift IDL:
service BinaryService {
binary fetchBlob(1: i64 id)
}
For both clients and servers you have three API choices to pick from:
MethodPerEndpointServicePerEndpointReqRepServicePerEndpoint
MethodPerEndpoint represents each Thrift service endpoint, or function, as
a method of the generated service class. The signature of each method will
closely match the corresponding function as defined in the Thrift IDL. One
difference though is that the return type of each method will be modified to
return a Future of the expected value, instead of the value directly. As an
example, the fetchBlob function of the BinaryService would be a method
which takes a Long named ‘id’ as an argument, and returns a Future
of a Java NIO ByteBuffer, with ByteBuffer being used to represent a
binary value.
ServicePerEndpoint represents each Thrift service endpoint, or function, as
a Finagle Service, instead of a method. This allows methods to be filtered,
either individually or collectively, as a Filter can be composed with a
Service but not a method. At its base, a Finagle Service is defined as a
function from a RequestType to a Future of a ResponseType.
Therefore, a call to a Service requires a single argument. To handle the
discrepancy with Thrift methods, where endpoints can require multiple arguments,
an Args class is generated, with each value being a named endpoint
parameter. For the return type of the Service, a type alias is used named
SuccessType, which represents the return type of the Thrift service
endpoint. As an example, the fetchBlob function of the BinaryService
would be a Service[FetchBlob.Args, FetchBlob.SuccessType]. The
FetchBlob.Args class would contain an ‘id’ value of type Long. Whereas
FetchBlob.SuccessType would be a type alias for Java NIO’s ByteBuffer.
ReqRepServicePerEndpoint is similar to ServicePerEndpoint, except that
each endpoint is represented by a Service from a |c.t.scrooge.Request|_ to
a |c.t.scrooge.Response|_. These envelope types allow for the passing of header
information between clients and servers, similar to the way HTTP request and
response headers work. Note that exchanging header information only works
when using the com.twitter.finagle.ThriftMux protocol.
Terminology¶
If you are still using the deprecated API constructs, we recommend switching to the non-deprecated code as it attempts to add clarity to the API.
Below is a guide which may be helpful for translating from the deprecated API to the non-deprecated.
Deprecated |
Replacement |
|---|---|
FutureIface |
MethodPerEndpoint |
MethodIface |
MethodPerEndpoint.apply() |
MethodIfaceBuilder |
MethodPerEndpointBuilder |
BaseServiceIface |
ServicePerEndpoint |
ServiceIface |
ServicePerEndpoint.apply() |
ServiceIfaceBuilder |
ServicePerEndpointBuilder |
– |
ReqRepServicePerEndpoint |
– |
ReqRepServicePerEndpoint.apply() |
– |
ReqRepServicePerEndpointBuilder |
Creating a Server¶
MethodPerEndpoint¶
To create a server, you need to provide an implementation of the MethodPerEndpoint
interface and use it with Finagle’s com.twitter.finagle.Thrift.Server
or com.twitter.finagle.ThriftMux.Server class.
class ServerImpl extends BinaryService.MethodPerEndpoint {
def fetchBlob(id: Long): Future[ByteBuffer] = {
??? // your implementation here
}
}
val server = Thrift.server.serveIface("host:port", new ServerImpl)
ServicePerEndpoint¶
Additionally, Scrooge generates a ServicePerEndpoint which is a trait
defining a Service for each Thrift method.
trait ServicePerEndpoint {
def fetchBlob: Service[BinaryService.FetchBlob.Args, BinaryService.FetchBlob.SuccessType])
Note that every method in the IDL becomes a Service for the corresponding
Args and SuccessType structures. The wrappers are needed to wrap multiple
method arguments into one type. Instead of implementing the MethodPerEndpoint interface
directly, you can provide an instance of the ServicePerEndpoint and convert it to
the MethodPerEndpoint interface using the toThriftService function.
val servicePerEndpoint: BinaryService.ServicePerEndpoint = new BinaryService.ServicePerEndpoint {
def fetchBlob: Service[BinaryService.FetchBlob.Args, BinaryService.FetchBlob.SuccessType] = ???
}
val methodPerEndpoint: BinaryService.MethodPerEndpoint = servicePerEndpoint.toThriftService
val service = Thrift.server.serveIface("host:port", methodPerEndpoint)
The advantage of this approach is that the Services can be decorated with
Filters.
val servicePerEndpoint: BinaryService.ServicePerEndpoint = new BinaryService.ServicePerEndpoint {
def fetchBlob: Service[BinaryService.FetchBlob.Args, BinaryService.FetchBlob.SuccessType] = ???
}
val loggingFilter: Filter[BinaryService.FetchBlob.Args, BinaryService.FetchBlob.SuccessType] = ???
val filteredServicePerEndpoint: BinaryService.ServicePerEndpoint =
servicePerEndpoint
.withFetchBlob(
fetchBlob = loggingFilter.andThen(servicePerEndpoint.fetchBlob)
)
val methodPerEndpoint: BinaryService.MethodPerEndpoint = filteredServicePerEndpoint.toThriftService
val service = Thrift.server.serveIface("host:port", methodPerEndpoint)
ReqRepServicePerEndpoint¶
Note
Only the ThriftMux protocol supports passing header information.
Lastly, Scrooge generates a ReqRepServicePerEndpoint which is also a trait
defining a Service for each Thrift method.
trait ReqRepServicePerEndpoint {
def fetchBlob: Service[Request[BinaryService.FetchBlob.Args], Response[BinaryService.FetchBlob.SuccessType]])
Note that every method in the IDL becomes a Service over c.t.scrooge.Request[Args] and
c.t.scrooge.Response[SuccessType] structures. These wrappers allow for the getting and setting
of header data that can be exchanged between a client and server. Again, instead of implementing the
MethodPerEndpoint interface directly, you can also provide an instance of the
ReqRepServicePerEndpoint and convert it to the MethodPerEndpoint interface using the
toThriftService function.
val reqRepServicePerEndpoint: BinaryService.ReqRepServicePerEndpoint = new BinaryService.ReqRepServicePerEndpoint {
def fetchBlob: Service[Request[BinaryService.FetchBlob.Args], Response[BinaryService.FetchBlob.SuccessType]] = ???
}
val methodPerEndpoint: BinaryService.MethodPerEndpoint = reqRepServicePerEndpoint.toThriftService
val service = ThriftMux.server.serveIface("host:port", methodPerEndpoint)
As true with the previous ServicePerEndpoint approach, this can also be decorated with Filters.
val reqRepServicePerEndpoint: BinaryService.ReqRepServicePerEndpoint = new BinaryService.ReqRepServicePerEndpoint {
def fetchBlob: Service[Request[BinaryService.FetchBlob.Args], Response[BinaryService.FetchBlob.SuccessType]] = ???
}
val loggingFilter: Filter[Request[BinaryService.FetchBlob.Args], Response[BinaryService.FetchBlob.SuccessType]] = ???
val filteredReqRepServicePerEndpoint: BinaryService.ReqRepServicePerEndpoint =
reqRepServicePerEndpoint
.withFetchBlob(
fetchBlob = loggingFilter.andThen(reqRepServicePerEndpoint.fetchBlob)
)
val methodPerEndpoint: BinaryService.MethodPerEndpoint = filteredReqRepServicePerEndpoint.toThriftService
val service = ThriftMux.server.serveIface("host:port", methodPerEndpoint)
Important
It is expected that a MethodPerEndpoint is what is eventually served to create a ListeningServer, e.g.,
Protocol.server.serveIface(…, methodPerEndpoint)
Thus, even when implementing a ServicePerEndpoint or a ReqRepServicePerEndpoint, the implementation
should be converted to a MethodPerEndpoint (via #toThriftService) then served.
Creating a Client¶
MethodPerEndpoint¶
Creating a client works similarly; you provide Finagle’s Thrift.Client
or ThriftMux.Client object the MethodPerEndpoint.
val methodPerEndpoint: BinaryService.MethodPerEndpoint =
Thrift.client.build[BinaryService.MethodPerEndpoint]("host:port")
val result: Future[ByteBuffer] = methodPerEndpoint.fetchBlob(12525L)
ServicePerEndpoint¶
Alternatively, you can request a ServicePerEndpoint instead.
val servicePerEndpoint: BinaryService.ServicePerEndpoint =
Thrift.client.servicePerEndpoint[BinaryService.ServicePerEndpoint]("host:port")
val result: Future[ByteBuffer] =
servicePerEndpoint.fetchBlob(BinaryService.FetchBlob.Args(12525L))
As in the server case, this allows you to decorate your client with Filters
and convert to the MethodPerEndpoint.
val timeoutFilter: Filter[BinaryService.FetchBlob.Args, BinaryService.FetchBlob.SuccessType] = ???
val filteredServicePerEndpoint: BinaryService.ServicePerEndpoint =
servicePerEndpoint
.withFetchBlob(
fetchBlob = timeoutFilter.andThen(servicePerEndpoint.fetchBlob)
)
val methodPerEndpoint: BinaryService.MethodPerEndpoint =
Thrift.Client.methodPerEndpoint(filteredServicePerEndpoint)
ReqRepServicePerEndpoint¶
Note
Only the ThriftMux protocol supports passing header information.
Lastly, you can request a ReqRepServicePerEndpoint.
val reqRepServicePerEndpoint: BinaryService.ReqRepServicePerEndpoint =
ThriftMux.client.servicePerEndpoint[BinaryService.ReqRepServicePerEndpoint]("host:port")
val result: Future[ByteBuffer] =
reqRepServicePerEndpoint.fetchBlob(
Request(BinaryService.FetchBlob.Args(12525L))
.setHeader("foo", "bar"))
As in the server case, this also allows you to decorate your client with Filters
and convert to the MethodPerEndpoint.
val timeoutFilter: Filter[Request[BinaryService.FetchBlob.Args], Response[BinaryService.FetchBlob.SuccessType]] = ???
val filteredReqRepServicePerEndpoint: BinaryService.ReqRepServicePerEndpoint =
reqRepServicePerEndpoint
.withFetchBlob(
fetchBlob = timeoutFilter.andThen(reqRepServicePerEndpoint.fetchBlob)
)
val methodPerEndpoint: BinaryService.MethodPerEndpoint =
ThriftMux.Client.methodPerEndpoint(filteredReqRepServicePerEndpoint)
Configuration¶
In both the server and client cases, you probably want to pass more
configuration parameters to the Finagle client (e.g. Thrift.client.withXXX),
please check the Finagle documentation
for tweaks once you get things to compile.
A common configuration is the Thrift TProtocolFactory which can
be set with com.twitter.finagle.Thrift.Server.withProtocolFactory
and com.twitter.finagle.Thrift.Client.withProtocolFactory
along with the same methods available for ThriftMux.