With the continuous news about the XOOM/PLATFORM you’ve probably been wondering how you can put the reactive goodness to use. Here I explain how the use XOOM/ACTORS to achieve asynchronous messaging between objects, which are implemented as actors.
First, create your own playground project to work with. If you create a Maven-based project you will need to place a dependency into your playground’s pom.xml file.
... io.vlingo vlingo-actors 1.2.20 ...
If you prefer Gradle over Maven, insert the following into your build.gradle:
dependencies { compile 'io.vlingo:vlingo-actors:1.2.20' } repositories { jcenter() }
Actually, also add this one for junit since you’ll be using it in this tutorial.
... junit junit 4.11 test ...
NOTE: If you would like to run with a reasonable default configuration, skip the following step of creating a vlingo-actors.properties file and move on to the Java source code implementation.
Now open your playground project. In the Maven src/test/resources directory (IDE folder), create a file named vlingo-actors.properties and place the following properties in it.
# vlingo-actors.properties for my playground plugin.name.queueMailbox = true plugin.queueMailbox.classname = io.vlingo.actors.plugin.mailbox.concurrentqueue.ConcurrentQueueMailboxPlugin plugin.queueMailbox.defaultMailbox = true plugin.queueMailbox.numberOfDispatchersFactor = 1 plugin.queueMailbox.dispatcherThrottlingCount = 10 plugin.name.jdkLogger = true plugin.jdkLogger.classname = io.vlingo.actors.plugin.logging.jdk.JDKLoggerPlugin plugin.jdkLogger.name = playground plugin.jdkLogger.defaultLogger = true plugin.jdkLogger.handler.classname = io.vlingo.actors.plugin.logging.jdk.DefaultHandler plugin.jdkLogger.handler.name = vlingo plugin.jdkLogger.handler.level = ALL
Now to the code. You need to create a few Java interfaces and classes. There’s a Java interface that acts as the type safe messaging protocol of the first actor that you will create. For now, create a Pinger interface with a single method definition, named ping, which takes a Ponger as a parameter.
package playground; import io.vlingo.actors.Stoppable; public interface Pinger extends Stoppable { void ping(final Ponger ponger); }
Next create a Ponger interface the same way, but with a pong() method that takes a Pinger as a parameter.
package playground; import io.vlingo.actors.Stoppable; public interface Ponger extends Stoppable { void pong(final Pinger pinger); }
Now you have two protocols. These define the type safe behaviors that one or more actors will implement, and the means by which clients will interact with the actors. In case it’s not obvious, Pinger is a client of Ponger, and Ponger is a client of Pinger.
It’s time to create two simple actors. First create one to implement the Pinger protocol.
package playground; import io.vlingo.actors.Actor; public class PingerActor extends Actor implements Pinger { private final Pinger self; public PingerActor() { self = selfAs(Pinger.class); } public void ping(final Ponger ponger) { ponger.pong(self); } }
After that, create another actor to implement the Ponger protocol.
package playground; import io.vlingo.actors.Actor; public class PongerActor extends Actor implements Ponger { private final Ponger self; public PongerActor() { self = selfAs(Ponger.class); } public void pong(final Pinger pinger) { pinger.ping(self); } }
You now have two actors that collaborate to play ping pong. The problem is that these actors will play ping pong nonstop, forever, unless we do something to prevent that. Doing so demonstrates how actors can maintain their own state, just like typical objects.
package playground; import io.vlingo.actors.Actor; public class PingerActor extends Actor implements Pinger { private int count; private final Pinger self; public PingerActor() { count = 0; self = selfAs(Pinger.class); } public void ping(final Ponger ponger) { if (++count >= 10) { self.stop(); ponger.stop(); } else { ponger.pong(self); } } }
Looking back at the Pinger and Ponger interface definitions, you will notice that both of these protocols extend the Stoppable protocol. Thus, they can both be stopped by other actors that have a Stoppable reference to them. We use that capability from within PingerActor to cause both actors to stop when the count reaches 10.
Note that in this case the actors are not required to implement their own stop() methods. That’s because the abstract base class, Actor, implements stop() for them. You could override stop() to find out when your actor is being stopped, but that’s not necessarily a good idea. What if you forgot to invoke the super’s stop()? That would make you think that your actor was going to stop, but the actor would never shut down because the Actor base class behavior would never be run. If you want to know when you are being stopped, you can override one of the four life cycle methods instead of stop().
package playground; import io.vlingo.actors.Actor; public class PingerActor extends Actor implements Pinger { private int count; private final Pinger self; public PingerActor() { count = 0; self = selfAs(Pinger.class); } public void ping(final Ponger ponger) { if (++count >= 10) { self.stop(); ponger.stop(); } else { ponger.pong(self); } } @Override protected void afterStop() { logger().log("Pinger " + address() + " just stopped!"); super.afterStop(); } }
All four life cycle methods are:
beforeStart()
afterStop()
beforeRestart(final Throwable reason)
afterRestart(final Throwable reason)
These enable you to see when significant life cycle events occur with your actor. The restart life cycle methods are related to actor supervision. When your actor’s supervisor sees your actor fail with an Exception, it can take a number of actions. Your supervisor can tell your actor to resume, to stop, or to restart. When it tells your actor to restart, the beforeRestart() is invoked first, and then the afterRestart() is invoked. Since your actor has failed, it may have been left in an invalid state. In such cases, these two life cycle methods give your actor the opportunity to clean up after itself and reinitialize itself before reacting to its next available protocol message.
The above afterStop() method shows two additional perks of XOOM/ACTORS. All actors have a personal address, which is available through your inherited address() method. Also, all actors have a Logger available via its logger() method.
Alright, we have two actors, but how do we bring the actors to life in the first place, and how do we get them to start collaborating in game play? Here’s how you start up the World for your actors to play in.
package playground; import org.junit.Test; import io.vlingo.actors.Definition; import io.vlingo.actors.World; public class PlaygroundTest { @Test public void testPlayPingPong() { final World world = World.start("playground"); final Pinger pinger = world.actorFor(Definition.has(PingerActor.class, Definition.NoParameters), Pinger.class); final Ponger ponger = world.actorFor(Definition.has(PongerActor.class, Definition.NoParameters), Ponger.class); pinger.ping(ponger); pauseThisThread(); world.terminate(); } }
When this test is run, a World is created. You can reference a previous post about the various major components of XOOM/ACTORS, one of which is World. In a nutshell, a World is the primary container within which actors live and play.
Next, two actors are created, and a reference to their respective protocol is returned. Each actor is created by means of a Definition. The Definition indicates the class of the actor that implements the protocol, such as PingerActor.class, which implements the Pinger.class protocol. Note that in this example neither actor takes constructor parameters. If they did, you would replace the Definition.NoParameters with a list of parameters in the order that the constructor takes them.
final Pinger pinger = world.actorFor( Definition.has( PingerActor.class, Definition.parameters("Hey, yo!", 42), "name-that-actor"), Pinger.class);
This demonstrates that a Definition can be used to define parameters and various actor characteristics, such as it’s text string name. Look over the source code for the several different ways that an actor can be created, including with a specific parent, a non-default logger, and a specialized supervisor.
One last point about the unit test is appropriate. As you noticed, a method named pauseThisThread() is used. Some sort of coordination is necessary because the actors send and receive all protocol messages asynchronously, and recall that there will be a total of 10 pings. Since the messages are all delivered and reacted to asynchronously, there is no “automatic” way to know when all the messages, including the stop() for both actors, have been delivered. In a later tutorial I will show you how you can more conveniently test actors without using such a “thread sleep” artifice. But for how just be aware that the Actor Model discards the blocking and synchronous programming model that most programmers are familiar and comfortable with. Don’t get used to this, but here is an implementation for this playground test.
... private void pauseThisThread() { try { Thread.sleep(100); } catch (Exception e) { } } ...
In order to make the ping pong produce some output, create some log output in the ping() and pong() methods:
// in Pinger ... public void ping(final Ponger ponger) { ++count; logger().log("ping " + count); if (count >= 10) { self.stop(); ponger.stop(); } else { ponger.pong(self); } } ... // in Ponger public void pong(final Pinger pinger) { logger().log("pong"); pinger.ping(self); } ...
I hope this brief tutorial has given you the incentive to dive into XOOM/ACTORS and discover the other beautiful and powerful programming experiences ahead of you!