以优雅的方式在运行时处理不同消息类型的负载

时间:2017-02-03 22:26:32

标签: scala generics protocol-buffers scalapb

为了能够处理大量不同的请求类型,我创建了一个.proto文件,如下所示:

message Message
{
   string typeId = 1;
   bytes message = 2;
}

我添加了typeId,以便了解实际protobuf bytes代表什么。 (自描述)

现在我的问题是以优雅的方式处理不同的“具体类型”。 (注意:如果我简单地使用类似switch-case的方法,一切正常!)

我想到了这样的解决方案:

1)具有不同处理者必须实现的特征,例如:

trait Handler[T]
{
  def handle(req: T): Any
}

object TestHandler extends Handler[Test]
{
  override def handle(req: Test): String =
  {
    s"A success, $req has been handled by TestHandler
  }
}

object OtherHandler extends Handler[Other]
{
  override def handle(req: Other): String =
  {
    s"A success, $req has been handled by OtherHandler
  }
} 

2)提供某种注册表来查询给定消息的正确处理程序:

val handlers = Map(
    Test -> TestHandler,
    Other -> OtherHandler
  )

3)如果一个请求进入它标识自己,所以我们需要另一个Mapper:

val reqMapper = Map(
  "Test" -> Test
  "Other" -> Other
)

4)如果有请求,请处理:

val request ...
// Determine the requestType
val requestType = reqMapper(request.type) 
// Find the correct handler for the requestType
val handler = handlers(requestType)
// Parse the actual request
val actualRequest = requestType.parse(...) // type of actualRequest can only be Test or Other in our little example

现在,直到这里一切看起来都很精致,但是这条线打破了我的整个世界:

handler.handle(actualRequest)

导致:

  

类型不匹配;发现:com.trueaccord.scalapb.GeneratedMessage with Product with com.trueaccord.scalapb.Message [_>:tld.test.proto.Message.Test with tld.test.proto.Message.Other<:com.trueaccord。 scalapb.GeneratedMessage with Product] with com.trueaccord.lenses.Updatable [_>:tld.test.proto.Message.Other with tld.test.proto.Message.Test&lt ;: com.trueaccord.scalapb.GeneratedMessage with Product ] {def companion:Serializable}必需:_1

据我了解 - 如果我错了请认真对待 - 编译器在这里无法确定,actualRequest是否由处理程序“处理”。这意味着它缺乏actualRequest肯定位于mapper的某个地方的知识,而且还有handler

它基本上是人类会得到的隐含知识,但编译器无法推断。

那就是说,我怎么能优雅地克服这种情况呢?

3 个答案:

答案 0 :(得分:2)

使用普通地图时,您的类型会丢失。例如

object Test{}
object Other{}
val reqMapper = Map("Test" -> Test,"Other" -> Other)
reqMapper("Test")
res0: Object = Test$@5bf0fe62 // the type is lost here and is set to java.lang.Object

最常见的方法是使用模式匹配

request match {
  case x: Test => TestHandler(x)
  case x: Other => OtherHandler(x)
  case _ => throw new IllegalArgumentException("not supported")
}

如果您仍想使用Google地图将类型存储到处理程序关系,请考虑Shapeless here提供的HMap

  

异质地图

     

Shapeless提供支持任意的异质地图   密钥类型与相应的值类型之间的关系,

答案 1 :(得分:1)

您可以使用的一个技巧是将伴随对象捕获为隐式对象,并将解析和处理组合在一个函数中,其中该类型可供编译器使用:

case class Handler[T <: GeneratedMessage with Message[T]](handler: T => Unit)(implicit cmp: GeneratedMessageCompanion[T]) {
  def handle(bytes: ByteString): Unit = {
    val msg: T = cmp.parseFrom(bytes.newInputStream)
    handler(t)
  }
}

val handlers: Map[String, Handler[_]] = Map(
  "X" -> Handler((x: X) => Unit),
  "Y" -> Handler((x: Y) => Unit)
)

// To handle the request:
handlers(request.typeId).handle(request.message)

另外,请查看any.proto,它定义了与您的Message非常相似的结构。它无法解决您的问题,但您可以利用它的packunpack方法。

答案 2 :(得分:0)

我现在已经解决了这个问题(基本上是相同的,有点适合我的特定用例)

trait Handler[T <: GeneratedMessage with Message[T], R]
{
    implicit val cmp: GeneratedMessageCompanion[T]
    def handle(bytes: ByteString): R = {
        val msg: T = cmp.parseFrom(bytes.newInput())
        handler(msg)
    }

    def apply(t: T): R
}

object Test extends Handler[Test, String]
{
    override def apply(t: Test): String = s"$t received and handled"

    override implicit val cmp: GeneratedMessageCompanion[Test] = Test.messageCompanion
}