Обработка нагрузок различных типов сообщений во время выполнения элегантным способом

Чтобы иметь возможность обрабатывать большое количество разных типов запросов, я создал .proto файл как это:

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

Я добавил typeId так что каждый знает, что актуально protobufbytes представляет собой. (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 методы.

Другие вопросы по тегам