vlingo/zoom

The vlingo/PLATFORM adoption accelerator for Java and other JVM languages.

Our vlingo/zoom component is an ongoing effort to help individual developers and architects, as well as whole teams, to speedily jump start learning on the vlingo/PLATFORM. We will continue to enhance the vlingo/zoom capabilities and introduce them here.

Async-Less Programming

We have found that one of the blockers to learning reactive programming is, well, non-blocking. As a large body of programmers, the software industry has mostly supplied and socialized blocking or synchronizing languages and tooling. Because of this, developers have come to expect blocking—and for many of us that write software in teams—you've likely either inherited or contributed to a legacy of blocking code in your projects. This legacy of blocking becomes continually more difficult to change. For these reasons, it comes as no surprise that a non-blocking approach will require a cooperative mind-bending effort for teams to fully adopt an asynchronous, concurrent, and parallelized programming culture.

For this reason, we've decided to introduce a familiar way to learn the different components of the vlingo/PLATFORM toolset—without the need to embrace the mind-bending multi-processing approach up front. We're keen on providing you with the stepping stones that help you and your team make the jump to parallelism without the added burden of learning an entirely new way of writing code. We welcome you to explore our vlingo/zoom tools so that you can quickly learn the ropes, gradually adding in asynchrony as you feel more comfortable.

It is our hope that this project will help you realize our platform's overarching goals of aggressive simplicity combined with lightweight fluency—with or without adopting Domain-Driven Design. After all, the best way to learn any language worth speaking is to learn both the words and the grammar as you go, helping you to achieve fluency through both gradual and rapid practice.

Messaging is at the core of the vlingo/PLATFORM, and as such, vlingo/zoom still uses messaging in the same way that you would with our asynchronous environment, but does so in a blocking way that you might be familiar with. The messages are all sent and received on a single thread.

Now, let's assume that a sending actor sends a message to a receiving actor. The receiving actor will see the message from the sending actor and react to it. After reacting to the message, the receiving actor will then return control back to the sending actor on the same thread. Let's now say that the receiving actor is a request handler that provides an HTTP response. As non-reactive blocking communication goes, all requests to the handler will be served responses on the same thread.

The only difference in this explanation above, to what you might be used to, is the idea of using actors as an abstraction instead of HTTP-based web services. Whether or not the underlying protocol, servlet, container, or driver enables reactive or blocking communication, the actor programming model itself is unchanged.

Now, let's take a look at the vlingo/zoom API to help you get started with making this leap towards using the other components of vlingo/PLATFORM.

Boot and Start Up

To begin, you need the following dependency for your builds, the first being for Maven.

<dependency>
<groupId>io.vlingo</groupId>
<artifactId>vlingo-zoom</artifactId>
<version>x.y.z</version>
<scope>compile</scope>
</dependency>

The second is for Gradle. In both examples, replace x, y, and z with the major version, minor version, and patch version, respectively, for the artifact.

dependencies {
compile 'io.vlingo:vlingo-zoom:x.y.z'
}

You also need the following configuration that is by default found in the properties filevlingo-zoom.properties provided with the repository and artifact distribution.

plugin.name.pooledCompletes = true
plugin.pooledCompletes.classname = io.vlingo.actors.plugin.completes.PooledCompletesPlugin
plugin.pooledCompletes.pool = 10
plugin.pooledCompletes.mailbox = queueMailbox
plugin.name.blockingMailbox = true
plugin.blockingMailbox.classname = io.vlingo.zoom.actors.plugin.mailbox.blocking.BlockingMailboxPlugin
plugin.blockingMailbox.defaultMailbox = true
plugin.name.slf4jLogger = true
plugin.slf4jLogger.classname = io.vlingo.actors.plugin.logging.slf4j.Slf4jLoggerPlugin
plugin.slf4jLogger.name = vlingo/zoom
plugin.slf4jLogger.defaultLogger = true
proxy.generated.classes.main = target/classes/
proxy.generated.sources.main = target/generated-sources/
proxy.generated.classes.test = target/test-classes/
proxy.generated.sources.test = target/generated-test-sources/

These are a subset of the properties required used by the vlingo/actors component and found in the vlingo-actors.properties file. These properties are necessary for vlingo/zoom to function properly. The lynchpin of vlingo/zoom is the plugin.name.blockingMailbox, which is described below.

To boot the vlingo/PLATFORM programming foundation, use the Boot API. There are two ways to do so. One way is through a void main(String[] args) boot method.

Boot.main(new String[] { "zoom-boot-world-name" });
final World world = Boot.zoomBootWorld();

Of course the main() would normally be invoked by the Java runtime. This demonstrates that if any command-line arguments are received, the first argument will be used to name the World. If there are no command-line arguments, the World will be named "vlingo-zoom".

A second way to boot the platform is to use the Boot.start(String name) method.

final World world = Boot.start("zoom-boot-world-name");

One of the many cool things about the vlingo/PLATFORM, whether using vlingo/zoom or the standard API, is that it actually boots within a few milliseconds. How cool is that? And you know that everybody loves cool!

Now you are ready to use the World and any living part of it to explore the marvels of the platform.

Messaging

Messaging is done the same simple way that it is with our standard platform environment. There are no surprises. Consider an example with a TestDeliveryProtocol.

public interface TestDeliveryProtocol {
void reactTo();
void reactTo(final int x, final int y, final int z);
void reactTo(final String text);
Completes<List<String>> reactions();
}

This protocol provides three command messages and a forth used for querying. The actor that provides this protocol can react in three ways: using no parameters, three integer parameters, and with one text string parameter. Here is an actor that implements this protocol.

public class TestDeliveryProtocolActor extends Actor implements TestDeliveryProtocol {
private final List<String> reactions;
public TestDeliveryProtocolActor() {
this.reactions = new ArrayList<>();
}
@Override
public void reactTo() {
final String reaction = "reacting to no parameters";
logger().debug(reaction);
reactions.add(reaction);
}
@Override
public void reactTo(final int x, final int y, final int z) {
final String reaction = "reacting to: x=" + x + " y=" + y + " z=" + z;
logger().debug(reaction);
reactions.add(reaction);
}
@Override
public void reactTo(final String text) {
final String reaction = "reacting to: text=" + text;
logger().debug(reaction);
reactions.add(reaction);
}
@Override
public Completes<List<String>> reactions() {
logger().debug("reactions...");
return completes().with(Collections.unmodifiableList(reactions));
}
}

Let's focus on the first three reactTo() message handlers. This actor does just a few things when it receives each message type. It formats a string to indicate what happened. It logs that message through the standard io.vlingo.actors.Logger protocol that is available to all io.vlingo.actors.Actor extenders. Each of the message handlers appends the formatted text description to a List held by its state. This state is held so that a client of this actor can query for all its reactions.

That leads to the forth and last message handler, reactions(). It answers the full List resulting from the various reactTo() messages received. Note that it doesn't just answer the raw, mutable List. Instead it provides an immutable List, so that its state cannot be tampered with by anyone on the outside. In addition, it answers the immutable List as a Completes<T> outcome. You can read in more detail about Completes<T> here. In brief it is a means for asynchronous query operations being executed on separate threads to provide eventual answers to a requester. Of course, this actor is running within a World running on vlingo/zoom, and thus is not asynchronous. Still, we don't change the entire platform API to accommodate use in a blocking environment. We want you to learn in comfort, but learn something new and experience the API in a safe haven.

Here is a test that shows a usage example.

final TestDeliveryProtocol test = world.actorFor(TestDeliveryProtocol.class, TestDeliveryProtocolActor.class);
test.reactTo();
test.reactTo(1, 2, 3);
test.reactTo("Hello, World!");
test.reactions().andThenConsume(reactions -> assertEquals(3, reactions.size()));

The test gets a reference through which it may send messages to the actor that supplies the TestDeliveryProtocol. The test then sends messages, each of the three types of reactTo() that can be sent. Finally, it asks the actor to answer its List of reactions. Note that the test client receives the outcome by using the andThenConsume(function). Inside this lambda an assertion is made that the List is expected to have three elements, one for each of the command messages sent.

All of the above is executed on a single thread; the thread that the test is running on.

Request-Response

The second example shows how request-response works. Not that you may think of the above query as request-response, and technically it is. Even so, here it means one actor that sends a request message to a second actor, and that second actor sends a response as a message back to the first actor. Here are the protocols.

public interface TestRequestProtocol extends Stoppable {
void request(final int value, final TestResponseProtocol respondTo);
}

The first one is the request handler. It may receive a request() and respond to the second protocol by sending it a response(). After some expected outcome of request-response, the second protocol can be queried for a total.

public interface TestResponseProtocol extends Stoppable {
Completes<Integer> total();
void response(final int value, final TestRequestProtocol requestOf);
}

The implementations of these two protocols are next.

public class TestRequestProtocolActor extends Actor implements TestRequestProtocol {
@Override
public void request(int value, TestResponseProtocol respondTo) {
respondTo.response(value + 1, selfAs(TestRequestProtocol.class));
}
}

This actor responds to the received request by adding 1 to the value it received. The response actor receives a response and tracks the total until a desired outcome.

public class TestResponseProtocolActor extends Actor implements TestResponseProtocol, Stoppable {
private final TestRequestProtocol requester;
private int total;
public TestResponseProtocolActor(final TestRequestProtocol requester) {
this.requester = requester;
this.total = 0;
}
@Override
public void start() {
requester.request(total, selfAs(TestResponseProtocol.class));
}
@Override
public void response(final int value, final TestRequestProtocol requestOf) {
if (value >= 10) {
total = value;
} else {
requestOf.request(value + 1, selfAs(TestResponseProtocol.class));
}
}
@Override
public Completes<Integer> total() {
return completes().with(total);
}
}

Note that the start() life cycle message is received just following construction. This is where the response actor starts the request-response process by sending a total of 0 to the request handler actor. The request-response continues until the response actor sees a value of 10 or greater.

The following is a test that shows an example usage.

final TestRequestProtocol requestOf = world.actorFor(TestRequestProtocol.class, TestRequestProtocolActor.class);
final TestResponseProtocol respondTo = world.actorFor(TestResponseProtocol.class, TestResponseProtocolActor.class, requestOf);
respondTo.total().andThenConsume(value -> assertTrue(10 <= value));

How We Async-Less

There's no magic here. The primary tool provided is a specialized actor mailbox. You can read more about actors and the role of their mailboxes here and here. The vlingo/actors tooling supports any number of mailbox plugins. The particular one provided by vlingo/zoom is a blocking actor mailbox.

io.vlingo.zoom.actors.plugin.mailbox.blocking.BlockingMailbox

This mailbox works by delivering a message to the target actor immediately rather than leaving the message for another thread to deliver. There is a potential problem with this. When you consider the above request-response example, what would happen if the request handling actor and the response handling actor continued indefinitely sending each other messages rather than stopping after 10? Correct, an ugly stack overflow would soon happen, or even re-entering an actor on the same thread and potentially modifying its state unexpectedly before the first message delivery has returned.

To prevent this the blocking mailbox does in fact implement a queue, but it protects access to the queue using a compare-and-set operation. This limits the queue polling to only the first arriving enqueuing access. Think of the same thread that delivers to the request actor, next delivering a message to the response actor on the same thread. As described previously, this could cause a number of problems. So, what should be done?

public class BlockingMailbox implements Mailbox {
...
public void send(final Message message) {
if (isClosed()) return;
queue.add(message);
if (isSuspended()) {
return;
}
try {
boolean deliver = true;
while (deliver) {
if (delivering.compareAndSet(false, true)) {
while (deliverAll())
;
delivering.set(false);
}
deliver = false;
}
} catch (Throwable t) {
if (delivering.get()) {
delivering.set(false);
}
throw new RuntimeException(t.getMessage(), t);
}
}
...
}

Consider the above send(Message message) method of the BlockingMailbox. The mailbox invocation that occurs first locks the mailbox queue, but without blocking another attempt to lock. The compare-and-set prevents blocking on any secondary deliveries. So a second, third, forth, etc., delivery on the same thread enqueues the message, and when it sees that access is already reserved it simply returns. When the message deliveries (method invocations) unwind, the original access will see all additional messages enqueued and deliver them. This could go on for a long time without causing issues, as long as the stack is given the opportunity to unwind before the queue causes an out-of-memory condition.