Skip to main content

How to make Pekko serialization bulletproof

Picture of Łukasz Kontowski, Junior Scala Developer

Łukasz Kontowski

Junior Scala Developer
May 16, 2023|12 min read
How_to_make_Pekko_serialization_bulletproof_cover
How_to_make_Pekko_serialization_bulletproof_Graph_1

1addSbtPlugin("org.virtuslab.psh" % "sbt-pekko-serialization-helper" % Version)
1lazy val app = (project in file("app"))
2 .enablePlugins(PekkoSerializationHelperPlugin)
akka-serialization-graphic

1package org
2trait MySer
1pekko.actor {
2 serializers {
3 jackson-json = "pekko.serialization.jackson.JacksonJsonSerializer"
4 }
5 serialization-bindings {
6 "org.MySer" = jackson-json
7 }
8}
1trait MySer
2case class MyMessage() // extends MySer
1@SerializabilityTrait
2trait MySerializable
3// It allows catching errors like these:
4import Pekko.actor.typed.Behavior
5
6object BehaviorTest {
7 sealed trait Command //extends MySerializable
8 def method(msg: Command): Behavior[Command] = ???
9}
1test0.scala:7: error: org.random.project.BehaviorTest.Command is used as Pekko message
2but does not extend a trait annotated with org.virtuslab.psh.annotation.SerializabilityTrait.
3Passing an object of a class that does NOT extend a trait annotated with SerializabilityTrait as a message may cause Pekko to
4fall back to Java serialization during runtime.
5
6
7 def method(msg: Command): Behavior[Command] = ???
8 ^
9test0.scala:6: error: Make sure this type is itself annotated, or extends a type annotated
10with @org.virtuslab.psh.annotation.SerializabilityTrait.
11 sealed trait Command extends MySerializable
12 ^
How_to_make_Pekko_serialization_bulletproof_Graph_2

1sbt ashDumpPersistenceSchema
1- name: org.random.project.Data
2 typeSymbol: trait
3- name: org.random.project.Data.ClassTest
4 typeSymbol: class
5 fields:
6 - name: a
7 typeName: java.lang.String
8 - name: b
9 typeName: scala.Int
10 - name: c
11 typeName: scala.Double
12 parents:
13 - org.random.project.Data
14- name: org.random.project.Data.ClassWithAdditionData
15 typeSymbol: class
16 fields:
17 - name: ad
18 typeName: org.random.project.Data.AdditionalData
19 parents:
20 - org.random.project.Data
easy-to-diff

1case class Message(animal: Animal) extends MySer
2
3sealed trait Animal
4
5final case class Lion(name: String) extends Animal
6final case class Tiger(name: String) extends Animal
1case class Message(animal: Animal) extends MultiDocPrintService
2
3@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
4@JsonSubTypes(
5 Array(
6 new JsonSubTypes.Type(value = classOf[Lion], name = "lion"),
7 new JsonSubTypes.Type(value = classOf[Tiger], name = "tiger")))
8sealed trait Animal
9
10final case class Lion(name: String) extends Animal
11final case class Tiger(name: String) extends Animal
1case object Tick
1actorRef ! Tick
2
3// Inside the actor:
4def receive = {
5 case Tick => // this won't get matched !!
6} // message will be unhandled !!
1import org.virtuslab.psh.PekkoSerializationHelperPlugin
2
3lazy val app = (project in file("app"))
4 // ...
5 .settings(libraryDependencies += PekkoSerializationHelperPlugin.circePekkoSerializer)
1import org.virtuslab.psh.circe.CircePekkoSerializer
2
3class ExampleSerializer(actorSystem: ExtendedActorSystem)
4 extends CircePekkoSerializer[MySerializable](actorSystem) {
5
6 override def identifier: Int = 41
7
8 override lazy val codecs = Seq(Register[CommandOne], Register[CommandTwo])
9
10 override lazy val manifestMigrations = Nil
11
12 override lazy val packagePrefix = "org.project"
13}
1pekko {
2 actor {
3 serializers {
4 circe-json = "org.example.ExampleSerializer"
5 }
6 serialization-bindings {
7 "org.example.MySerializable" = circe-json
8 }
9 }
10}
1import org.virtuslab.psh.circe.CircePekkoSerializer
2import org.virtuslab.psh.circe.Register
3
4class ExampleSerializer(actorSystem: ExtendedActorSystem)
5 extends CircePekkoSerializer[MySerializable](actorSystem) {
6 // ...
7 override lazy val codecs = Seq(Register[CommandOne]) // WHOOPS someone forgot to register CommandTwo...
8}
1java.lang.RuntimeException: Serialization of [CommandTwo] failed. Call Register[A]
2for this class or its supertype and append the result to `def codecs`.
3
1import org.virtuslab.psh.circe.CircePekkoSerializer
2import org.virtuslab.psh.circe.Register
3
4@Serializer(
5 classOf[MySerializable],
6 typeRegexPattern = Register.REGISTRATION_REGEX)
7class ExampleSerializer(actorSystem: ExtendedActorSystem)
8 extends CircePekkoSerializer[MySerializable](actorSystem) {
9 // ...
10 override lazy val codecs = Seq(Register[CommandOne]) // WHOOPS someone forgot to register CommandTwo...
11 // ... but Codec Registration Checker will throw a compilation error here:
12 // `No codec for `CommandOne` is registered in a class annotated with @org.virtuslab.psh.annotation.Serializer`
13}

Curated by Sebastian Synowiec

Subscribe to our newsletter and never miss an article