在运行时以优雅的方式处理不同消息类型的负载
Handling loads of different message-types at runtime in an elegant way
为了能够处理大量不同的请求类型,我创建了一个 .proto
文件,如下所示:
message Message
{
string typeId = 1;
bytes message = 2;
}
我添加了 typeId
以便人们知道实际的 protobuf
bytes
代表什么。 (自我描述)
现在我的问题是以优雅的方式处理不同的 "concrete types"。 (注意:如果我简单地使用类似 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)
它导致:
type mismatch; found : 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 <: com.trueaccord.scalapb.GeneratedMessage with Product]{def companion: Serializable} required: _1
据我了解 - 如果有误请在此处更正 - 编译器在这里无法确定 actualRequest
是 "handable"处理程序。这意味着它不知道 actualRequest
肯定在那个 mapper
中的某个地方,并且还不知道它有一个 handler
。
这基本上是人类可以获得的隐含知识,但编译器无法推断。
所以,话虽这么说,我怎样才能优雅地克服这种情况?
当您使用普通地图时,您的类型会丢失。例如
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")
}
如果您仍想使用映射来存储您的类型与处理程序的关系,请考虑由 Shapeless here
提供的 HMap
Heterogenous maps
Shapeless provides a heterogenous map which supports an arbitrary
relation between the key type and the corresponding value type,
您可以使用的一个技巧是将伴随对象捕获为隐式对象,并将解析和处理组合在一个函数中,其中类型可供编译器使用:
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
非常相似。它不会解决您的问题,但您可以利用它的 pack
和 unpack
方法。
我暂时选择了这个解决方案(基本上是 thesamet 的,稍微适应了我的特定 use-case)
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
}
为了能够处理大量不同的请求类型,我创建了一个 .proto
文件,如下所示:
message Message
{
string typeId = 1;
bytes message = 2;
}
我添加了 typeId
以便人们知道实际的 protobuf
bytes
代表什么。 (自我描述)
现在我的问题是以优雅的方式处理不同的 "concrete types"。 (注意:如果我简单地使用类似 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)
它导致:
type mismatch; found : 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 <: com.trueaccord.scalapb.GeneratedMessage with Product]{def companion: Serializable} required: _1
据我了解 - 如果有误请在此处更正 - 编译器在这里无法确定 actualRequest
是 "handable"处理程序。这意味着它不知道 actualRequest
肯定在那个 mapper
中的某个地方,并且还不知道它有一个 handler
。
这基本上是人类可以获得的隐含知识,但编译器无法推断。
所以,话虽这么说,我怎样才能优雅地克服这种情况?
当您使用普通地图时,您的类型会丢失。例如
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")
}
如果您仍想使用映射来存储您的类型与处理程序的关系,请考虑由 Shapeless here
提供的HMap
Heterogenous maps
Shapeless provides a heterogenous map which supports an arbitrary relation between the key type and the corresponding value type,
您可以使用的一个技巧是将伴随对象捕获为隐式对象,并将解析和处理组合在一个函数中,其中类型可供编译器使用:
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
非常相似。它不会解决您的问题,但您可以利用它的 pack
和 unpack
方法。
我暂时选择了这个解决方案(基本上是 thesamet 的,稍微适应了我的特定 use-case)
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
}