* Serializable RemoteActorRef - Serialized to an immutable, network-aware Actor reference that can be freely shared across the network. They "remember" and stay mapped to their original Actor instance and host node, and will always work as expected.
* Serializable LocalActorRef - Serialized by doing a deep copy of both the ActorRef and the Actor instance itself. Can be used to physically move an Actor from one node to another and continue the execution there.
Both of these can be sent as messages over the network and/or store them to disk, in a persistent storage backend etc.
Actor serialization in Akka is implemented through a type class 'Format[T <: Actor]' which publishes the 'fromBinary' and 'toBinary' methods for serialization. Here's the complete definition of the type class:
..code-block:: scala
/**
* Type class definition for Actor Serialization
*/
trait FromBinary[T <: Actor] {
def fromBinary(bytes: Array[Byte], act: T): T
}
trait ToBinary[T <: Actor] {
def toBinary(t: T): Array[Byte]
}
// client needs to implement Format[] for the respective actor
trait Format[T <: Actor] extends FromBinary[T] with ToBinary[T]
**Deep serialization of an Actor and ActorRef**
-----------------------------------------------
You can serialize the whole actor deeply, e.g. both the 'ActorRef' and then instance of its 'Actor'. This can be useful if you want to move an actor from one node to another, or if you want to store away an actor, with its state, into a database.
(actor2 !! "hello").getOrElse("_") should equal("world 3")
}
**Serialization of a RemoteActorRef**
-------------------------------------
You can serialize an 'ActorRef' to an immutable, network-aware Actor reference that can be freely shared across the network, a reference that "remembers" and stay mapped to its original Actor instance and host node, and will always work as expected.
The 'RemoteActorRef' serialization is based upon Protobuf (Google Protocol Buffers) and you don't need to do anything to use it, it works on any 'ActorRef' (as long as the actor has **not** implemented one of the 'SerializableActor' traits, since then deep serialization will happen).
Currently Akka will **not** autodetect an 'ActorRef' as part of your message and serialize it for you automatically, so you have to do that manually or as part of your custom serialization mechanisms.
Here is an example of how to serialize an Actor.
..code-block:: scala
val actor1 = actorOf[MyActor]
val bytes = toBinary(actor1)
To deserialize the 'ActorRef' to a 'RemoteActorRef' you need to use the 'fromBinaryToRemoteActorRef(bytes: Array[Byte])' method on the 'ActorRef' companion object:
..code-block:: scala
import RemoteActorSerialization._
val actor2 = fromBinaryToRemoteActorRef(bytes)
You can also pass in a class loader to load the 'ActorRef' class and dependencies from:
..code-block:: scala
import RemoteActorSerialization._
val actor2 = fromBinaryToRemoteActorRef(bytes, classLoader)
Deep serialization of a TypedActor
----------------------------------
Serialization of typed actors works almost the same way as untyped actors. You can serialize the whole actor deeply, e.g. both the 'proxied ActorRef' and the instance of its 'TypedActor'.
Here is the example from above implemented as a TypedActor.
^
Step 1: Define the actor
^^^^^^^^^^^^^^^^^^^^^^^^
..code-block:: scala
trait MyTypedActor {
def requestReply(s: String) : String
def oneWay() : Unit
}
class MyTypedActorImpl extends TypedActor with MyTypedActor {
val typedActor1 = TypedActor.newInstance(classOf[MyTypedActor], classOf[MyTypedActorImpl], 1000)
val f = new MyTypedActorFormat
val bytes = toBinaryJ(typedActor1, f)
val typedActor2: MyTypedActor = fromBinaryJ(bytes, f) //type hint needed
typedActor2.requestReply("hello")
-
Serialization of a remote typed ActorRef
----------------------------------------
To deserialize the TypedActor to a 'RemoteTypedActorRef' (an aspectwerkz proxy to a RemoteActorRef) you need to use the 'fromBinaryToRemoteTypedActorRef(bytes: Array[Byte])' method on 'RemoteTypedActorSerialization' object:
..code-block:: scala
import RemoteTypedActorSerialization._
val typedActor = fromBinaryToRemoteTypedActorRef(bytes)
// you can also pass in a class loader
val typedActor2 = fromBinaryToRemoteTypedActorRef(bytes, classLoader)
Compression
===========
Akka has a helper class for doing compression of binary data. This can be useful for example when storing data in one of the backing storages. It currently supports LZF which is a very fast compression algorithm suited for runtime dynamic compression.
Here is an example of how it can be used:
..code-block:: scala
import akka.serialization.Compression
val bytes: Array[Byte] = ...
val compressBytes = Compression.LZF.compress(bytes)
val uncompressBytes = Compression.LZF.uncompress(compressBytes)
Using the Serializable trait and Serializer class for custom serialization
If you are sending messages to a remote Actor and these messages implement one of the predefined interfaces/traits in the 'akka.serialization.Serializable.*' object, then Akka will transparently detect which serialization format it should use as wire protocol and will automatically serialize and deserialize the message according to this protocol.
Each serialization interface/trait in
* akka.serialization.Serializable.*
> has a matching serializer in
* akka.serialization.Serializer.*
Note however that if you are using one of the Serializable interfaces then you don’t have to do anything else in regard to sending remote messages.
The ones currently supported are (besides the default which is regular Java serialization):
Apart from the above, Akka also supports Scala object serialization through `SJSON <http://github.com/debasishg/sjson/tree/master>`_ that implements APIs similar to 'akka.serialization.Serializer.*'. See the section on SJSON below for details.
Protobuf
--------
Akka supports using `Google Protocol Buffers <http://code.google.com/p/protobuf>`_ to serialize your objects. Protobuf is a very efficient network serialization protocol which is also used internally by Akka. The remote actors understand Protobuf messages so if you just send them as they are they will be correctly serialized and unserialized.
Here is an example.
Let's say you have this Protobuf message specification that you want to use as message between remote actors. First you need to compiled it with 'protoc' compiler.
..code-block:: scala
message ProtobufPOJO {
required uint64 id = 1;
required string name = 2;
required bool status = 3;
}
When you compile the spec you will among other things get a message builder. You then use this builder to create the messages to send over the wire:
..code-block:: scala
val result = remoteActor !! ProtobufPOJO.newBuilder
.setId(11)
.setStatus(true)
.setName("Coltrane")
.build
The remote Actor can then receive the Protobuf message typed as-is:
..code-block:: scala
class MyRemoteActor extends Actor {
def receive = {
case pojo: ProtobufPOJO =>
val id = pojo.getId
val status = pojo.getStatus
val name = pojo.getName
...
}
}
JSON: Scala
-----------
Use the akka.serialization.Serialization.ScalaJSON base class with its toJSON method. Akka’s Scala JSON is based upon the SJSON library.
For your POJOs to be able to serialize themselves you have to extend the ScalaJSON[] trait as follows. JSON serialization is based on a type class protocol which you need to define for your own abstraction. The instance of the type class is defined as an implicit object which is used for serialization and de-serialization. You also need to implement the methods in terms of the APIs which sjson publishes.
..code-block:: scala
import akka.serialization.Serializer
import akka.serialization.Serializable.ScalaJSON
import scala.reflect.BeanInfo
case class MyMessage(val id: String, val value: Tuple2[String, Int]) extends ScalaJSON[MyMessage] {
// type class instance
implicit val MyMessageFormat: sjson.json.Format[MyMessage] =
it("should be able to serialize and de-serialize MyMessage") {
val s = MyMessage("Target", ("cooker", 120))
s.fromBytes(s.toBytes) should equal(s)
s.fromJSON(s.toJSON) should equal(s)
}
Use akka.serialization.Serializer.ScalaJSON to do generic JSON serialization, e.g. serialize object that does not extend ScalaJSON using the JSON serializer. Serialization using Serializer can be done in two ways :-
1. Type class based serialization (recommended)
2. Reflection based serialization
We will discuss both of these techniques in this section. For more details refer to the discussion in the next section SJSON: Scala.
Serializer API using type classes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Here are the steps that you need to follow:
1. Define your class
..code-block:: scala
case class MyMessage(val id: String, val value: Tuple2[String, Int])
2. Define the type class instance
..code-block:: scala
import DefaultProtocol._
implicit val MyMessageFormat: sjson.json.Format[MyMessage] =
You can also use the Serializer abstraction to serialize using the reflection based serialization API of sjson. But we recommend using the type class based one, because reflection based serialization has limitations due to type erasure. Here's an example of reflection based serialization:
def this() = this(null) // default constructor is necessary for deserialization
}
val foo = new Foo("bar")
val json = Serializer.ScalaJSON.out(foo)
val fooCopy = Serializer.ScalaJSON.in(json) // returns a JsObject as an AnyRef
val fooCopy2 = Serializer.ScalaJSON.in(new String(json)) // can also take a string as input
val fooCopy3 = Serializer.ScalaJSON.in[Foo](json).asInstanceOf[Foo]
Classes without a @BeanInfo annotation cannot be serialized as JSON.
So if you see something like that:
..code-block:: scala
scala> Serializer.ScalaJSON.out(bar)
Serializer.ScalaJSON.out(bar)
java.lang.UnsupportedOperationException: Class class Bar not supported for conversion
at sjson.json.JsBean$class.toJSON(JsBean.scala:210)
at sjson.json.Serializer$SJSON$.toJSON(Serializer.scala:107)
at sjson.json.Serializer$SJSON$class.out(Serializer.scala:37)
at sjson.json.Serializer$SJSON$.out(Serializer.scala:107)
at akka.serialization.Serializer$ScalaJSON...
it means, that you haven't got a @BeanInfo annotation on your class.
You may also see this exception when trying to serialize a case class with out an attribute like this:
..code-block:: scala
@BeanInfo case class Empty() // cannot be serialized
SJSON: Scala
-------------
SJSON supports serialization of Scala objects into JSON. It implements support for built in Scala structures like List, Map or String as well as custom objects. SJSON is available as an Apache 2 licensed project on Github `here <http://github.com/debasishg/sjson/tree/master>`_.
Example: I have a Scala object as ..
..code-block:: scala
val addr = Address("Market Street", "San Francisco", "956871")
where Address is a custom class defined by the user. Using SJSON, I can store it as JSON and retrieve as plain old Scala object. Here’s the simple assertion that validates the invariant. Note that during de-serialziation, the class name is specified. Hence what it gives back is an instance of Address.
..code-block:: scala
addr should equal(
serializer.in[Address](serializer.out(addr)))
Note, that the class needs to have a default constructor. Otherwise the deserialization into the specified class will fail.
There are situations, particularly when writing generic persistence libraries in Akka, when the exact class is not known during de-serialization. Using SJSON I can get it as AnyRef or Nothing ..
..code-block:: scala
serializer.in[AnyRef](serializer.out(addr))
or just as ..
..code-block:: scala
serializer.in(serializer.out(addr))
What you get back from is a JsValue, an abstraction of the JSON object model. For details of JsValueimplementation, refer to `dispatch-json <http://databinder.net/dispatch/About>`_ that SJSON uses as the underlying JSON parser implementation. Once I have the JsValue model, I can use use extractors to get back individual attributes ..
..code-block:: scala
val a = serializer.in[AnyRef](serializer.out(addr))
// use extractors
val c = 'city ? str
val c(_city) = a
_city should equal("San Francisco")
val s = 'street ? str
val s(_street) = a
_street should equal("Market Street")
val z = 'zip ? str
val z(_zip) = a
_zip should equal("956871")
Serialization of Embedded Objects
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SJSON supports serialization of Scala objects that have other embedded objects. Suppose you have the following Scala classes .. Here Contact has an embedded Address Map ..
The annotation @JSONProperty can be used to selectively ignore fields. When I serialize a Journal object out and then back in, the content of issn field will be null.
..code-block:: scala
it("should ignore issn field") {
val j = Journal(100, "IEEE Computer", "Alex Payne", "012-456372")
serializer.in[Journal](serializer.out(j)).asInstanceOf[Journal].issn should equal(null)
}
Similarly, we can ignore properties of an object **only** if they are null and not ignore otherwise. Just specify the annotation @JSONProperty as @JSONProperty {val ignoreIfNull = true}.
^
Serialization with Type Hints for Generic Data Members
val classLoader = //.. specify a custom classloader
}
import SJSON._
serializer.out(..)
//..
Fighting Type Erasure
^^^^^^^^^^^^^^^^^^^^^
Because of type erasure, it's not always possible to infer the correct type during de-serialization of objects. Consider the following example:
..code-block:: scala
abstract class A
@BeanInfo case class B(param1: String) extends A
@BeanInfo case class C(param1: String, param2: String) extends A
@BeanInfo case class D(@(JSONTypeHint @field)(value = classOf[A])param1: List[A])
and the serialization code like the following:
..code-block:: scala
object TestSerialize{
def main(args: Array[String]) {
val test1 = new D(List(B("hello1")))
val json = sjson.json.Serializer.SJSON.out(test1)
val res = sjson.json.Serializer.SJSON.in[D](json)
val res1: D = res.asInstanceOf[D]
println(res1)
}
}
Note that the type hint on class D says A, but the actual instances that have been put into the object before serialization is one of the derived classes (B). During de-serialization, we have no idea of what can be inside D. The serializer.in API will fail since all hint it has is for A, which is abstract. In such cases, we need to handle the de-serialization by using extractors over the underlying data structure that we use for storing JSON objects, which is JsValue. Here's an example:
..code-block:: scala
val test1 = new D(List(B("hello1")))
val json = serializer.out(test1)
// create a JsValue from the string
val js = Js(new String(json))
// extract the named list argument
val m = (Symbol("param1") ? list)
val m(_m) = js
// extract the string within
val s = (Symbol("param1") ? str)
// form a list of B's
val result = _m.map{ e =>
val s(_s) = e
B(_s)
}
// form a D
println("result = " + D(result))
The above snippet de-serializes correctly using extractors defined on JsValue. For more details on JsValue and the extractors, please refer to `dispatch-json <http://databinder.net/dispatch/About>`_ .
**NOTE**: Serialization with SJSON is based on bean introspection. In the current version of Scala (2.8.0.Beta1 and 2.7.7) there is a bug where bean introspection does not work properly for classes enclosed within another class. Please ensure that the beans are the top level classes in your application. They can be within objects though. A ticket has been filed in the Scala Tracker and also fixed in the trunk. Here's the `ticket <https://lampsvn.epfl.ch/trac/scala/ticket/3080>`_ .
Type class based Serialization
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If type erasure hits you, reflection based serialization may not be the right option. In fact the last section shows some of the scenarios which may not be possible to handle using reflection based serialization of sjson. sjson also supports type class based serialization where you can provide a custom protocol for serialization as part of the type class implementation.
Here's a sample session at the REPL which shows the default serialization protocol of sjson:
..code-block:: scala
scala> import sjson.json._
import sjson.json._
scala> import DefaultProtocol._
import DefaultProtocol._
scala> val str = "debasish"
str: java.lang.String = debasish
scala> import JsonSerialization._
import JsonSerialization._
scala> tojson(str)
res0: dispatch.json.JsValue = "debasish"
scala> fromjson[String](res0)
res1: String = debasish
You can use serialization of generic data types using the default protocol as well:
..code-block:: scala
scala> val list = List(10, 12, 14, 18)
list: List[Int] = List(10, 12, 14, 18)
scala> tojson(list)
res2: dispatch.json.JsValue = [10, 12, 14, 18]
scala> fromjson[List[Int]](res2)
res3: List[Int] = List(10, 12, 14, 18)
You can also define your own custom protocol, which as to be an implementation of the following type class:
..code-block:: scala
trait Writes[T] {
def writes(o: T): JsValue
}
trait Reads[T] {
def reads(json: JsValue): T
}
trait Format[T] extends Writes[T] with Reads[T]
Consider a case class and a custom protocol to serialize it into JSON. Here's the type class implementation:
..code-block:: scala
object Protocols {
case class Person(lastName: String, firstName: String, age: Int)
There are other nifty ways to implement case class serialization using sjson. For more details, have a look at the `wiki <http://wiki.github.com/debasishg/sjson/typeclass-based-json-serialization>`_ for sjson.
Use the akka.serialization.SerializerFactory.getJavaJSON to do generic JSON serialization, e.g. serialize object that does not extend JavaJSON using the JSON serializer.
To serialize Scala structures you can use SBinary serializer. SBinary can serialize all primitives and most default Scala datastructures; such as List, Tuple, Map, Set, BigInt etc.
Here is an example of using the akka.serialization.Serializer.SBinary serializer to serialize standard Scala library objects.
..code-block:: scala
import akka.serialization.Serializer
import sbinary.DefaultProtocol._ // you always need to import these implicits
val users = List(("user1", "passwd1"), ("user2", "passwd2"), ("user3", "passwd3"))
val bytes = Serializer.SBinary.out(users)
val usersCopy = Serializer.SBinary.in(bytes, Some(classOf[List[Tuple2[String,String]]]))
If you need to serialize your own user-defined objects then you have to do three things:
# Define an empty constructor
# Mix in the Serializable.SBinary[T] trait, and implement its methods:
## fromBytes(bytes: Array[Byte])[T]
## toBytes: Array[Byte]
# Create an implicit sbinary.Format[T] object for your class. Which means that you have to define its two methods:
## reads(in: Input): T; in which you read in all the fields in your object, using read[FieldType](in)and recreate it.