我试图为scala的thrift中的AsyncClient
创建一个通用适配器,它将让rpc实现Function1[A, Future[B]]
而不是使用thrift的可组合内置回调方法。由于生成的thrift类的apis在很大程度上是非泛型的,所以这很有挑战性,因此为任意thrift客户端创建简单的通用包装并不简单。请考虑以下示例:
class AsyncThriftClient[A, B, X](
requestConsumer: (A, AsyncMethodCallback[X]) => Unit,
callbackResultConverter: X => B) extends Function1[A, Future[B]] {
private class Callback(p: Promise[B])
extends AsyncMethodCallback[X] {
def onComplete(x: X): Unit = {
try {
val result = callbackResultConverter(x)
println("from server: " + result)
p.success(result)
} catch {
case e: Exception => p.failure(e)
}
}
def onError(e: Exception): Unit = {
p.failure(e)
}
}
def apply(request: A): Future[B] = {
val p = Promise[B]
requestConsumer(request, new Callback(p))
p.future
}
}
def main(args: Array[String]) {
try {
val ex = Executors.newSingleThreadExecutor
implicit val ec = ExecutionContext.fromExecutor(ex)
val client = new ServiceStatus.AsyncClient(
new TBinaryProtocol.Factory,
new TAsyncClientManager,
new TNonblockingSocket("localhost", 9090))
val fun = new AsyncThriftClient[ //
StatusRequest, StatusResponse, ServiceStatus.AsyncClient.status_call](
client.status(_, _),
_.getResult)
val request = new StatusRequest("say hi")
val fut = fun(request)
fut.onSuccess { case r => println(s"succ $r") }
fut.onFailure { case e => println(s"erro $e") }
Thread.sleep(1000)
ex.shutdown()
} catch {
case e: Exception => e.printStackTrace()
}
}
这似乎是合理的第一次尝试,但请注意绑定到X
的类型参数ServiceStatus.AsyncClient.status_call
。似乎我不应该提供这个,因为它对AsyncThriftClient
中的任何方法签名都不重要。我真正需要的是说应该有"某种类型" X
使得以下构造函数参数一致,这听起来很像存在类型。生成的调用站点如下所示:
val client = new ServiceStatus.AsyncClient(
new TBinaryProtocol.Factory,
new TAsyncClientManager,
new TNonblockingSocket("localhost", 9090))
val fun = new AsyncThriftClient[StatusRequest, StatusResponse](
client.status(_, _),
_.getResult)
并且编译器会发现有一个合适的X
可以让client.status(_, _)
和_.getResult
匹配。有没有办法实现这个目标? (顺便说一句,后续任务是封装client
的实例化,这可能需要类似的技术。)
答案 0 :(得分:2)
我将整个事情包装在一个抽象的API代理中,并将中间Thrift类型的规范和类型转换函数的实现留给具体实现。像这样:
trait AsyncThriftAPI[A,B] {
protected type X // Intermediate Thrift type; not for use outside API implementations
// Implementor must specify these.
protected def sendRequest(in: A, callback: AsyncMethodCallback[X]): Unit
protected def convertResult(intermediate: X): B
// Note that up here, we never use the client directly,
// so let's not needlessly couple this API proxy pattern
// to too many transport dependencies
// final because of "must be abstract or final" dogma :)
final def apply(request: A): Future[B] = {
val p = Promise[B]
sendRequest(request, new Callback(p))
p.future
}
private class Callback(p: Promise[B]) extends AsyncMethodCallback[X] {
def onComplete(x: X): Unit = {
try {
val result = convertResult(x)
println("from server: " + result)
p.success(result)
} catch {
case e: Exception => p.failure(e)
}
}
def onError(e: Exception): Unit = {
p.failure(e)
}
}
}
现在实施它:
final class StatusAPI(implicit val transport: TNonblockingTransport,
val clientManager: TAsyncClientManager,
val protocolFactory: TProtocolFactory)
extends AsyncThriftAPI[StatusRequest, StatusResponse]
{
protected type X = ServiceStatus.AsyncClient.status_call
// Lazy so that we don't bother to spin it up until someone actually calls the API
lazy val client = new ServiceStatus.AsyncClient(protocolFactory,
clientManager,
transport)
protected def sendRequest(in: A, callback: AsyncMethodCallback[X]): Unit = client.status(in, callback)
protected def convertResult(intermediate: X) = intermediate.getResult
}
在通话现场:
// Have these in scope somewhere near the root of whichever component
// needs to connect to a variety of Thrift servers
implicit val protoFactory = new TBinaryProtocol.Factory
implicit val clientManager = new TAsyncClientManager
implicit val transport = new TNonblockingSocket("localhost", 9090))
val statusApi = new StatusAPI()
statusApi(new StatusRequest(...)) // returns Future[StatusResponse]
我没有尝试编译这个,如果有任何错误,请告诉我。
如果是我,我可能想要将一堆不同的相关API调用捆绑到一个API代理中,这样可能需要额外的抽象层。 :)