如何使用Scala的类型系统删除不必要的类型参数并创建一个通用的thrift api?

时间:2014-07-17 01:07:48

标签: scala thrift existential-type

我试图为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的实例化,这可能需要类似的技术。)

1 个答案:

答案 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代理中,这样可能需要额外的抽象层。 :)