Обработка нагрузок различных типов сообщений во время выполнения элегантным способом
Чтобы иметь возможность обрабатывать большое количество разных типов запросов, я создал .proto
файл как это:
message Message
{
string typeId = 1;
bytes message = 2;
}
Я добавил typeId
так что каждый знает, что актуально protobuf
bytes
представляет собой. (Self-описание)
Теперь моя проблема заключается в том, чтобы элегантно обрабатывать разные "конкретные типы". (Примечание: все работает нормально, если я просто использую 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 с продуктом с com.trueaccord.scalapb.Message[_ >: tld.test.proto.Message.Test с tld.test.proto.Message.Other <: com.trueaccord.scalapb. Сгенерированное сообщение с продуктом] с com.trueaccord.lenses.Updatable [_>: tld.test.proto.Message.Other с tld.test.proto.Message.Test <: com.trueaccord.scalapb.GeneratedMessage с продуктом] {def companion: Сериализуемый} требуется: _1
Насколько я понимаю - ПОЖАЛУЙСТА, ПОПРАВЬТЕ МЕНЯ ЗДЕСЬ, ЕСЛИ НЕПРАВИЛЬНО - компилятор не может быть здесь уверен, что actualRequest
"handable" обработчиком. Это означает, что ему не хватает знаний о том, что actualRequest
определенно где-то в этом mapper
И ТАКЖЕ, что есть handler
для этого.
Это в основном неявное знание, которое получил бы человек, но компилятор не может сделать вывод.
Итак, как говорится, как я могу элегантно преодолеть эту ситуацию?
3 ответа
Я остановился на этом решении на данный момент (в основном тот же, немного адаптированный для моего конкретного случая использования)
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
}
Ваши типы теряются, когда вы используете нормальную карту. например
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")
}
Если вы по-прежнему хотите использовать Карты для хранения вашего типа в отношении обработчика, рассмотрите HMap
предоставленный Shapeless здесь
Гетерогенные карты
Shapeless предоставляет гетерогенную карту, которая поддерживает произвольную связь между типом ключа и соответствующим типом значения,
Один из приемов, который вы можете использовать, - это захватить объект-компаньон как неявный и объединить разбор и обработку в одной функции, где тип доступен компилятору:
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
методы.