Merge branch 'wip-1456-document-typed-actors-√'
This commit is contained in:
commit
6c96397711
16 changed files with 632 additions and 418 deletions
154
akka-docs/scala/code/akka/docs/actor/TypedActorDocSpec.scala
Normal file
154
akka-docs/scala/code/akka/docs/actor/TypedActorDocSpec.scala
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
package akka.docs.actor
|
||||
|
||||
//#imports
|
||||
import akka.dispatch.{ Promise, Future, Await }
|
||||
import akka.util.duration._
|
||||
import akka.actor.{ ActorContext, TypedActor, Props }
|
||||
|
||||
//#imports
|
||||
|
||||
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.testkit._
|
||||
|
||||
//#typed-actor-iface
|
||||
trait Squarer {
|
||||
//#typed-actor-iface-methods
|
||||
def squareDontCare(i: Int): Unit //fire-forget
|
||||
|
||||
def square(i: Int): Future[Int] //non-blocking send-request-reply
|
||||
|
||||
def squareNowPlease(i: Int): Option[Int] //blocking send-request-reply
|
||||
|
||||
def squareNow(i: Int): Int //blocking send-request-reply
|
||||
//#typed-actor-iface-methods
|
||||
}
|
||||
//#typed-actor-iface
|
||||
|
||||
//#typed-actor-impl
|
||||
class SquarerImpl(val name: String) extends Squarer {
|
||||
|
||||
def this() = this("default")
|
||||
//#typed-actor-impl-methods
|
||||
import TypedActor.dispatcher //So we can create Promises
|
||||
|
||||
def squareDontCare(i: Int): Unit = i * i //Nobody cares :(
|
||||
|
||||
def square(i: Int): Future[Int] = Promise successful i * i
|
||||
|
||||
def squareNowPlease(i: Int): Option[Int] = Some(i * i)
|
||||
|
||||
def squareNow(i: Int): Int = i * i
|
||||
//#typed-actor-impl-methods
|
||||
}
|
||||
//#typed-actor-impl
|
||||
|
||||
//#typed-actor-supercharge
|
||||
trait Foo {
|
||||
def doFoo(times: Int): Unit = println("doFoo(" + times + ")")
|
||||
}
|
||||
|
||||
trait Bar {
|
||||
import TypedActor.dispatcher //So we have an implicit dispatcher for our Promise
|
||||
def doBar(str: String): Future[String] = Promise successful str.toUpperCase
|
||||
}
|
||||
|
||||
class FooBar extends Foo with Bar
|
||||
//#typed-actor-supercharge
|
||||
|
||||
class TypedActorDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) {
|
||||
|
||||
"get the TypedActor extension" in {
|
||||
val someReference: AnyRef = null
|
||||
|
||||
try {
|
||||
//#typed-actor-extension-tools
|
||||
|
||||
import akka.actor.TypedActor
|
||||
|
||||
//Returns the Typed Actor Extension
|
||||
val extension = TypedActor(system) //system is an instance of ActorSystem
|
||||
|
||||
//Returns whether the reference is a Typed Actor Proxy or not
|
||||
TypedActor(system).isTypedActor(someReference)
|
||||
|
||||
//Returns the backing Akka Actor behind an external Typed Actor Proxy
|
||||
TypedActor(system).getActorRefFor(someReference)
|
||||
|
||||
//Returns the current ActorContext,
|
||||
// method only valid within methods of a TypedActor implementation
|
||||
val c: ActorContext = TypedActor.context
|
||||
|
||||
//Returns the external proxy of the current Typed Actor,
|
||||
// method only valid within methods of a TypedActor implementation
|
||||
val s: Squarer = TypedActor.self[Squarer]
|
||||
|
||||
//Returns a contextual instance of the Typed Actor Extension
|
||||
//this means that if you create other Typed Actors with this,
|
||||
//they will become children to the current Typed Actor.
|
||||
TypedActor(TypedActor.context)
|
||||
|
||||
//#typed-actor-extension-tools
|
||||
} catch {
|
||||
case e: Exception ⇒ //dun care
|
||||
}
|
||||
}
|
||||
|
||||
"create a typed actor" in {
|
||||
//#typed-actor-create1
|
||||
val mySquarer: Squarer =
|
||||
TypedActor(system).typedActorOf[Squarer, SquarerImpl]()
|
||||
//#typed-actor-create1
|
||||
//#typed-actor-create2
|
||||
val otherSquarer: Squarer =
|
||||
TypedActor(system).typedActorOf(classOf[Squarer],
|
||||
new SquarerImpl("foo"),
|
||||
Props(),
|
||||
"name")
|
||||
//#typed-actor-create2
|
||||
|
||||
//#typed-actor-calls
|
||||
//#typed-actor-call-oneway
|
||||
mySquarer.squareDontCare(10)
|
||||
//#typed-actor-call-oneway
|
||||
|
||||
//#typed-actor-call-future
|
||||
val fSquare = mySquarer.square(10) //A Future[Int]
|
||||
//#typed-actor-call-future
|
||||
|
||||
//#typed-actor-call-option
|
||||
val oSquare = mySquarer.squareNowPlease(10) //Option[Int]
|
||||
//#typed-actor-call-option
|
||||
|
||||
//#typed-actor-call-strict
|
||||
val iSquare = mySquarer.squareNow(10) //Int
|
||||
//#typed-actor-call-strict
|
||||
//#typed-actor-calls
|
||||
|
||||
Await.result(fSquare, 3 seconds) must be === 100
|
||||
|
||||
oSquare must be === Some(100)
|
||||
|
||||
iSquare must be === 100
|
||||
|
||||
//#typed-actor-stop
|
||||
TypedActor(system).stop(mySquarer)
|
||||
//#typed-actor-stop
|
||||
|
||||
//#typed-actor-poisonpill
|
||||
TypedActor(system).poisonPill(otherSquarer)
|
||||
//#typed-actor-poisonpill
|
||||
}
|
||||
|
||||
"supercharge" in {
|
||||
//#typed-actor-supercharge-usage
|
||||
val awesomeFooBar = TypedActor(system).typedActorOf[Foo with Bar, FooBar]()
|
||||
|
||||
awesomeFooBar.doFoo(10)
|
||||
val f = awesomeFooBar.doBar("yes")
|
||||
|
||||
TypedActor(system).poisonPill(awesomeFooBar)
|
||||
//#typed-actor-supercharge-usage
|
||||
Await.result(f, 3 seconds) must be === "YES"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,186 +5,171 @@ Typed Actors (Scala)
|
|||
|
||||
.. contents:: :local:
|
||||
|
||||
The Typed Actors are implemented through `Typed Actors <http://en.wikipedia.org/wiki/Active_object>`_. It uses AOP through `AspectWerkz <http://aspectwerkz.codehaus.org/>`_ to turn regular POJOs into asynchronous non-blocking Actors with semantics of the Actor Model. Each method dispatch is turned into a message that is put on a queue to be processed by the Typed Actor sequentially one by one.
|
||||
Akka Typed Actors is an implementation of the `Active Objects <http://en.wikipedia.org/wiki/Active_object>`_ pattern.
|
||||
Essentially turning method invocations into asynchronous dispatch instead of synchronous that has been the default way since Smalltalk came out.
|
||||
|
||||
If you are using the `Spring Framework <http://springsource.org>`_ then take a look at Akka's `Spring integration <spring-integration>`_.
|
||||
Typed Actors consist of 2 "parts", a public interface and an implementation, and if you've done any work in "enterprise" Java, this will be very familiar to you. As with normal Actors you have an external API (the public interface instance) that will delegate methodcalls asynchronously to
|
||||
a private instance of the implementation.
|
||||
|
||||
**WARNING:** Do not configure to use a ``BalancingDispatcher`` with your ``TypedActors``, it just isn't safe with how ``TypedActors`` currently are implemented. This limitation will most likely be removed in the future.
|
||||
The advantage of Typed Actors vs. Actors is that with TypedActors you have a static contract, and don't need to define your own messages, the downside is that it places some limitations on what you can do and what you can't, i.e. you can't use become/unbecome.
|
||||
|
||||
Typed Actors are implemented using `JDK Proxies <http://docs.oracle.com/javase/6/docs/api/java/lang/reflect/Proxy.html>`_ which provide a pretty easy-worked API to intercept method calls.
|
||||
|
||||
.. note::
|
||||
|
||||
Just as with regular Akka Actors, Typed Actors process one call at a time.
|
||||
|
||||
|
||||
The tools of the trade
|
||||
----------------------
|
||||
|
||||
Before we create our first Typed Actor we should first go through the tools that we have at our disposal,
|
||||
it's located in ``akka.actor.TypedActor``.
|
||||
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: typed-actor-extension-tools
|
||||
|
||||
.. warning::
|
||||
|
||||
Same as not exposing ``this`` of an Akka Actor, it's important not to expose ``this`` of a Typed Actor,
|
||||
instead you should pass the external proxy reference, which is obtained from within your Typed Actor as
|
||||
``TypedActor.self``, this is your external identity, as the ``ActorRef`` is the external identity of
|
||||
an Akka Actor.
|
||||
|
||||
Creating Typed Actors
|
||||
---------------------
|
||||
|
||||
**IMPORTANT:** The Typed Actors class must have access modifier 'public' (which is default) and can't be an inner class (unless it is an inner class in an 'object').
|
||||
To create a Typed Actor you need to have one or more interfaces, and one implementation.
|
||||
|
||||
Akka turns POJOs with interface and implementation into asynchronous (Typed) Actors. Akka is using `AspectWerkz’s Proxy <http://blogs.codehaus.org/people/jboner/archives/000914_awproxy_proxy_on_steriods.html>`_ implementation, which is the `most performant <http://docs.codehaus.org/display/AW/AOP+Benchmark>`_ proxy implementation there exists.
|
||||
Our example interface:
|
||||
|
||||
In order to create a Typed Actor you have to subclass the ``TypedActor`` base class.
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: imports,typed-actor-iface
|
||||
:exclude: typed-actor-iface-methods
|
||||
|
||||
Here is an example.
|
||||
Our example implementation of that interface:
|
||||
|
||||
If you have a POJO with an interface implementation separation like this:
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: imports,typed-actor-impl
|
||||
:exclude: typed-actor-impl-methods
|
||||
|
||||
.. code-block:: scala
|
||||
The most trivial way of creating a Typed Actor instance
|
||||
of our Squarer:
|
||||
|
||||
import akka.actor.TypedActor
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: typed-actor-create1
|
||||
|
||||
trait RegistrationService {
|
||||
def register(user: User, cred: Credentials): Unit
|
||||
def getUserFor(username: String): User
|
||||
}
|
||||
First type is the type of the proxy, the second type is the type of the implementation.
|
||||
If you need to call a specific constructor you do it like this:
|
||||
|
||||
.. code-block:: scala
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: typed-actor-create2
|
||||
|
||||
public class RegistrationServiceImpl extends TypedActor with RegistrationService {
|
||||
def register(user: User, cred: Credentials) {
|
||||
... // register user
|
||||
}
|
||||
Since you supply a Props, you can specify which dispatcher to use, what the default timeout should be used and more.
|
||||
Now, our Squarer doesn't have any methods, so we'd better add those.
|
||||
|
||||
def getUserFor(username: String): User = {
|
||||
... // fetch user by username
|
||||
user
|
||||
}
|
||||
}
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: imports,typed-actor-iface
|
||||
|
||||
Then you can create an Typed Actor out of it by creating it through the ``TypedActor`` factory like this:
|
||||
Alright, now we've got some methods we can call, but we need to implement those in SquarerImpl.
|
||||
|
||||
.. code-block:: scala
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: imports,typed-actor-impl
|
||||
|
||||
val service = TypedActor.newInstance(classOf[RegistrationService], classOf[RegistrationServiceImpl], 1000)
|
||||
// The last parameter defines the timeout for Future calls
|
||||
Excellent, now we have an interface and an implementation of that interface,
|
||||
and we know how to create a Typed Actor from that, so let's look at calling these methods.
|
||||
|
||||
Creating Typed Actors with non-default constructor
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Method dispatch semantics
|
||||
-------------------------
|
||||
|
||||
To create a typed actor that takes constructor arguments use a variant of ``newInstance`` or ``newRemoteInstance`` that takes a call-by-name block in which you can create the Typed Actor in any way you like.
|
||||
Methods returning:
|
||||
|
||||
Here is an example:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
val service = TypedActor.newInstance(classOf[Service], new ServiceWithConstructorArgs("someString", 500L))
|
||||
|
||||
Configuration factory class
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Using a configuration object:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.actor.TypedActorConfiguration
|
||||
import akka.util.Duration
|
||||
import akka.util.duration._
|
||||
|
||||
val config = TypedActorConfiguration()
|
||||
.timeout(3000 millis)
|
||||
|
||||
val service = TypedActor.newInstance(classOf[RegistrationService], classOf[RegistrationServiceImpl], config)
|
||||
|
||||
However, often you will not use these factory methods but declaratively define the Typed Actors as part of a supervisor hierarchy. More on that in the :ref:`fault-tolerance-scala` section.
|
||||
|
||||
Sending messages
|
||||
----------------
|
||||
|
||||
Messages are sent simply by invoking methods on the POJO, which is proxy to the "real" POJO now. The arguments to the method are bundled up atomically into an message and sent to the receiver (the actual POJO instance).
|
||||
|
||||
One-way message send
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Methods that return void are turned into ‘fire-and-forget’ semantics by asynchronously firing off the message and return immediately. In the example above it would be the 'register' method, so if this method is invoked then it returns immediately:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
// method invocation returns immediately and method is invoke asynchronously using the Actor Model semantics
|
||||
service.register(user, creds)
|
||||
|
||||
Request-reply message send
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Methods that return something (e.g. non-void methods) are turned into ‘send-and-receive-eventually’ semantics by asynchronously firing off the message and wait on the reply using a Future.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
// method invocation is asynchronously dispatched using the Actor Model semantics,
|
||||
// but it blocks waiting on a Future to be resolved in the background
|
||||
val user = service.getUser(username)
|
||||
|
||||
Generally it is preferred to use fire-forget messages as much as possible since they will never block, e.g. consume a resource by waiting. But sometimes they are neat to use since they:
|
||||
|
||||
* Simulates standard Java method dispatch, which is more intuitive for most Java developers
|
||||
* Are a neat to model request-reply
|
||||
* Are useful when you need to do things in a defined order
|
||||
|
||||
Request-reply-with-future message send
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Methods that return a ``akka.dispatch.Future<TYPE>`` are turned into ‘send-and-receive-with-future’ semantics by asynchronously firing off the message and returns immediately with a Future. You need to use the ``future(...)`` method in the ``TypedActor`` base class to resolve the Future that the client code is waiting on.
|
||||
|
||||
Here is an example:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
class MathTypedActorImpl extends TypedActor with MathTypedActor {
|
||||
def square(x: Int): Future[Integer] = future(x * x)
|
||||
}
|
||||
|
||||
// create the ping actor
|
||||
val math = TypedActor.newInstance(classOf[MathTyped], classOf[MathTypedImpl])
|
||||
|
||||
// This method will return immediately when called, caller should wait on the Future for the result
|
||||
val future = math.square(10)
|
||||
future.await
|
||||
val result: Int = future.get
|
||||
|
||||
Stopping Typed Actors
|
||||
---------------------
|
||||
|
||||
Once Typed Actors have been created with one of the ``TypedActor.newInstance`` methods they need to be stopped with ``TypedActor.stop`` to free resources allocated by the created Typed Actor (this is not needed when the Typed Actor is supervised).
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
// Create Typed Actor
|
||||
val service = TypedActor.newInstance(classOf[RegistrationService], classOf[RegistrationServiceImpl], 1000)
|
||||
|
||||
// ...
|
||||
|
||||
// Free Typed Actor resources
|
||||
TypedActor.stop(service)
|
||||
|
||||
When the Typed Actor defines a shutdown callback method (:ref:`fault-tolerance-scala`) it will be invoked on ``TypedActor.stop``.
|
||||
|
||||
How to use the TypedActorContext for runtime information access
|
||||
---------------------------------------------------------------
|
||||
|
||||
The ``akka.actor.TypedActorContext`` class Holds 'runtime type information' (RTTI) for the Typed Actor. This context is a member field in the ``TypedActor`` base class and holds for example the current sender reference, the current sender future etc.
|
||||
|
||||
Here is an example how you can use it to in a 'void' (e.g. fire-forget) method to implement request-reply by using the sender reference:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
class PingImpl extends TypedActor with Ping {
|
||||
|
||||
def hit(count: Int) {
|
||||
val pong = context.getSender.asInstanceOf[Pong]
|
||||
pong.hit(count++)
|
||||
}
|
||||
}
|
||||
|
||||
If the sender, sender future etc. is not available, then these methods will return ``null`` so you should have a way of dealing with that scenario.
|
||||
* ``Unit`` will be dispatched with ``fire-and-forget`` semantics, exactly like ``ActorRef.tell``
|
||||
* ``akka.dispatch.Future[_]`` will use ``send-request-reply`` semantics, exactly like ``ActorRef.ask``
|
||||
* ``scala.Option[_]`` or ``akka.japi.Option<?>`` will use ``send-request-reply`` semantics, but *will* block to wait for an answer,
|
||||
and return None if no answer was produced within the timout, or scala.Some/akka.japi.Some containing the result otherwise.
|
||||
Any exception that was thrown during this call will be rethrown.
|
||||
* Any other type of value will use ``send-request-reply`` semantics, but *will* block to wait for an answer,
|
||||
throwing ``java.util.concurrent.TimeoutException`` if there was a timeout or rethrow any exception that was thrown during this call.
|
||||
|
||||
Messages and immutability
|
||||
-------------------------
|
||||
|
||||
**IMPORTANT**: Messages can be any kind of object but have to be immutable (there is a workaround, see next section). Java or Scala can’t enforce immutability (yet) so this has to be by convention. Primitives like String, int, Long are always immutable. Apart from these you have to create your own immutable objects to send as messages. If you pass on a reference to an instance that is mutable then this instance can be modified concurrently by two different Typed Actors and the Actor model is broken leaving you with NO guarantees and most likely corrupt data.
|
||||
While Akka cannot enforce that the parameters to the methods of your Typed Actors are immutable,
|
||||
we *strongly* recommend that parameters passed are immutable.
|
||||
|
||||
Akka can help you in this regard. It allows you to turn on an option for serializing all messages, e.g. all parameters to the Typed Actor effectively making a deep clone/copy of the parameters. This will make sending mutable messages completely safe. This option is turned on in the :ref:`configuration` file like this:
|
||||
One-way message send
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: ruby
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: typed-actor-call-oneway
|
||||
|
||||
akka {
|
||||
actor {
|
||||
# does a deep clone of messages to ensure immutability
|
||||
serialize-messages = on
|
||||
}
|
||||
}
|
||||
As simple as that! The method will be executed on another thread; asynchronously.
|
||||
|
||||
This will make a deep clone (using Java serialization) of all parameters.
|
||||
Request-reply message send
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: typed-actor-call-option
|
||||
|
||||
This will block for as long as the timeout that was set in the Props of the Typed Actor,
|
||||
if needed. It will return ``None`` if a timeout occurs.
|
||||
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: typed-actor-call-strict
|
||||
|
||||
This will block for as long as the timeout that was set in the Props of the Typed Actor,
|
||||
if needed. It will throw a ``java.util.concurrent.TimeoutException`` if a timeout occurs.
|
||||
|
||||
Request-reply-with-future message send
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: typed-actor-call-future
|
||||
|
||||
This call is asynchronous, and the Future returned can be used for asynchronous composition.
|
||||
|
||||
Stopping Typed Actors
|
||||
---------------------
|
||||
|
||||
Since Akkas Typed Actors are backed by Akka Actors they must be stopped when they aren't needed anymore.
|
||||
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: typed-actor-stop
|
||||
|
||||
This asynchronously stops the Typed Actor associated with the specified proxy ASAP.
|
||||
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala
|
||||
:include: typed-actor-poisonpill
|
||||
|
||||
This asynchronously stops the Typed Actor associated with the specified proxy
|
||||
after it's done with all calls that were made prior to this call.
|
||||
|
||||
Typed Actor Hierarchies
|
||||
-----------------------
|
||||
|
||||
Since you can obtain a contextual Typed Actor Extension by passing in an ``ActorContext``
|
||||
you can create child Typed Actors by invoking ``typedActorOf(..)`` on that.
|
||||
|
||||
This also works for creating child Typed Actors in regular Akka Actors.
|
||||
|
||||
Lifecycle callbacks
|
||||
-------------------
|
||||
|
||||
By having your Typed Actor implementation class implement any and all of the following:
|
||||
|
||||
* ``TypedActor.PreStart``
|
||||
* ``TypedActor.PostStop``
|
||||
* ``TypedActor.PreRestart``
|
||||
* ``TypedActor.PostRestart``
|
||||
|
||||
You can hook into the lifecycle of your Typed Actor.
|
||||
|
||||
Supercharging
|
||||
-------------
|
||||
|
||||
Here's an example on how you can use traits to mix in behavior in your Typed Actors.
|
||||
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala#typed-actor-supercharge
|
||||
|
||||
.. includecode:: code/akka/docs/actor/TypedActorDocSpec.scala#typed-actor-supercharge-usage
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue