У меня есть постоянный актер, который может получать команду одного типа Persist(event)
, где событие имеет тип trait Event
(существует множество его реализаций). И в случае успеха это возвращает Persisted(event)
отправителю.
Само событие является сериализуемым, поскольку это данные, которые мы храним в постоянном хранилище, а сериализация реализуется с помощью настраиваемого сериализатора, который внутренне использует классы, созданные из файлов google protobuf .proto
. И этот настраиваемый сериализатор настроен в application.conf
и привязан к базовому признаку Event
. Это уже нормально работает.
Примечание. Реализации Event
- это не классы, генерируемые protobuf. Это обычные классы scala, и у них тоже есть их эквивалент в protobuf, но он отображается через настраиваемый сериализатор, привязанный к базовому типу событий. Это было сделано моими предшественниками для управления версиями (что, вероятно, не требуется, потому что с этим можно справиться с помощью простых классов protobuf + настраиваемая комбинация сериализации, но это другое дело), и я не хочу менять этот банкомат.
Сейчас мы пытаемся реализовать сегментирование кластера для этого актора, что также означает, что мои команды (а именно Persist
и Persisted
) также должны быть сериализуемыми, поскольку они могут быть перенаправлены на другие узлы.
Это модель предметной области:
sealed trait PersistenceCommand {
def event: Event
}
final case class Persisted(event: Event) extends PersistenceCommand
final case class Persist(event: Event) extends PersistenceCommand
Проблема в том, что я не вижу элегантного способа сделать его сериализуемым. Ниже приведены варианты, которые я рассмотрел
Подход 1. Определите новый файл прототипа для Persist
и Persisted
, но что мне использовать в качестве типа данных для event
? Я не нашел способа определить что-то вроде этого:
message Persist {
"com.example.Event" event = 1 // this doesn't work
}
Таким образом, я могу использовать существующую черту Scala Event
в качестве типа данных. Если это сработает, я думаю (хотя это и надумано) я мог бы привязать сгенерированный код (после компиляции этого прото-файла) к встроенному сериализатору akka для google protobuf, и это может сработать. Приведенное выше примечание объясняет, почему я не могу использовать конструкцию oneof
в моем прото-файле.
Подход 2. Это то, что я реализовал, и он работает (но мне это не нравится)
По сути, я написал новый сериализатор для команд и делегировал сераизализацию и десериализацию event
части команды существующему сериализатору.
class PersistenceCommandSerializer extends SerializerWithStringManifest {
val eventSerializer: ManifestAwareEventSerializer = new ManifestAwareEventSerializer()
val PersistManifest = Persist.getClass.getName
val PersistedManifest = Persisted.getClass.getName
val Separator = "~"
override def identifier: Int = 808653986
override def manifest(o: AnyRef): String = o match {
case Persist(event) => s"$PersistManifest$Separator${eventSerializer.manifest(event)}"
case Persisted(event) => s"$PersistedManifest$Separator${eventSerializer.manifest(event)}"
}
override def toBinary(o: AnyRef): Array[Byte] = o match {
case command: PersistenceCommand => eventSerializer.toBinary(command.event)
}
override def fromBinary(bytes: Array[Byte], manifest: String): AnyRef = {
val (commandManifest, dataManifest) = splitIntoCommandAndDataManifests(manifest)
val event = eventSerializer.fromBinary(bytes, dataManifest).asInstanceOf[Event]
commandManifest match {
case PersistManifest =>
Persist(event)
case PersistedManifest =>
Persisted(event)
}
}
private def splitIntoCommandAndDataManifests(manifest: String) = {
val commandAndDataManifests = manifest.split(Separator)
(commandAndDataManifests(0), commandAndDataManifests(1))
}
}
Проблема с этим подходом - это то, что я делаю в def manifest
и def fromBinary
. Я должен был убедиться, что у меня есть манифест команды, а также манифест события при сериализации и десериализации. Следовательно, мне пришлось использовать ~
в качестве разделителя - своего рода мой собственный метод сериализации для информации манифеста.
Есть ли лучший или, возможно, правильный способ реализовать это?
Для контекста: я использую ScalaPB для создания классов scala из файлов .proto
и классических актеров akka.
Мы очень ценим любое руководство!
Approach 1
, пробовали ли выoneof
protobuf, например,protobuf message Persists { oneof event { EventTypeX = 1; EventTypeY = 2; ... } }
- person Sava Vranešević   schedule 13.02.2020Event
. Это было сделано моими предшественниками для управления версиями (что, вероятно, не требуется, потому что с этим можно справиться с помощью простых классов protobuf + настраиваемая комбинация сериализации, но это другое дело), и я не хочу менять этот банкомат. - person Niks   schedule 13.02.2020