Common Tools

Some common tools reused by multiple VLINGO XOOM platform components.

Common Tools

This is an overview of the more important tools found in XOOM Common.

Completes

The Completes<T> interface defines the means to receive and react to the results from an asynchronous operation at some time in the future. Using the word "future" may be a giveaway to this tool, because in various languages and toolkits this concept is known as Future<T>. We purposely avoided the use of "future" terminology, because the preexisting components work inconsistently and also house some complex naming and behavior that we, of course, simplified. Here is how to put Completes<T> to good use.

final Completes<Integer> result = calculator.factorialOf(42);

This example sends a message to an Actor asynchronously. Because the delivery to the underlying Actor that implements the Calculator protocol behaviors is asynchronous, there is no way for the result of the behavior to be known immediately. The contract to the client is a result that expresses the intention to later provide an Integer result. The eventual answer of type Integer is a parameter to Completes. After the message factorialOf(n) has been received by the Actor and the calculation has completed, the Actor answers the result, which is also delivered asynchronously. Eventually the internals of the Completes<T> implementation sees the calculated result, at which time it hands the result to any functions that have been registered by the client with the Completes<T>.

As you can see in the above example, no such functions have been registered, so the eventual outcome is lost. Let's rectify that omission.

final Completes<Integer> result = calculator.factorialOf(42);

result.andThenConsume(factorial -> System.out.println("Factorial of 42: " + factorial));

This is not a very interesting usage of the factorial result, but it does show that when the result has completed and been delivered to the Completes<T> internals, the client is given the opportunity to do something with the value. In this case the client simply prints the result. This can be expressed in a more condensed fashion.

calculator
  .factorialOf(42)
  .andThenConsume(factorial ->
      System.out.println("Factorial of 42: " + factorial));

The andThenConsume(c) is a means to register a Consumer<Integer> that does not produce a return value. There are other means to register functions, and the majority are concerned mostly with pipelining results from one kind of filter to another.

calculator
  .factorialOf(42)
  .andThen(factorial -> factorial * factorial)
  .andThenTo(squared -> calculator.factorialOf(squared))
  .andThenConsume(factorial -> System.out.println("Factorial: " + factorial));

The first andThen(f) registers a Function<T,R> that will be applied synchronously when the factorial is available. The factorial result is the T parameter and the R return value becomes the result of andThen(f). This result, which is the square of the original factorial calculation, becomes the input to the andThenTo(f). Notice the difference in naming. The andThen(f) is synchronous, but the andThenTo(f) handles an asynchronous result. This is used to pass the squared outcome of andThen(f) to the calculator to determine the factorial of the "factorial of 42 and then squared"-value. In other words, the andThenTo(f) is expecting you to send a message to an actor that will produce another eventual result. The Actor and protocol used could be any one, it's just that here the Calculator protocol and implementing Actor was reused.

Finally, after the second factorial is calculated, the result is consumed and printed to the console.

The following are the optional and required parameters that may be passed to andThen(). The exact same optional and required parameters are available to andThenTo().

Completes andThen(final long timeout, final T failedValue, final Function function); 
Completes andThen(final T failedValue, final Function function); 
Completes andThen(final long timeout, final Function function); 
Completes andThen(final Function function);

Note that andThen(f) as well as andThenTo(f) support the same parameters. However, the andThen(f) requires a return value of Completes<T>, while andThenTo(f) allows returning a different type that is parameterized by the client sender.

You can express whether or not a given operation should complete within a specific time frame, and if it does not, that a failure value will be the outcome rather than a normal result. How do you know whether the timeout has occurred and the failed value is the result?

reservations
  .reserveTravel(ticketInfo)
  .andThenTo(2_000, Failed.value(), reservation -> booking.record(reservation))
  .otherwiseConsume(failed -> traveler.inform(ticketInfo, failed));

When a timeout occurs, the otherwiseConsume() is invoked rather than the function that is registered with andThenTo().

The methods otherwise(failedValue) and otherwiseConsume(failedValue) are available. If you provide a timeout threshold but not a failedValue, the failedValue defaults to null.

Optionally, the Actor may answer the failedValue as a result of its behavior, which will also cause the otherwise(failedValue) to be invoked. Thus, it is not only a timeout that can possibly cause the otherwise() handler to be invoked.

For more information about the Completes<T> facilities take a look at the interface definition found in the xoom-common project (repository), in io.vlingo.xoom.common.Completes.

Beware of consuming Completes<T> results inside an Actor. When the function of theandThen(f) or other andThen???(f) is applied, the thread is not one assigned to the Actor for receiving a message. Rather, the thread is one assigned to a background Actor specifically designated to deliver the Completes<T> results to the andThen()function. Thus, it is quite possible/likely that the function is applied at the same time that the enclosing Actor is handling a message delivery via its mailbox for an implemented protocol. If both threads simultaneously modify the state of the Actor there will be races on state access and mutation. This will inevitably cause data corruption.

If your Actor must consume a Completes<T> results, you should self-send that result to a protocol designed for this purpose. For example,

// inside an actor
reservations
  .reserveTravel(ticketInfo)
  .andThenTo(2_000, Failed.value(), reservation ->
      selfAs(Booking.class).record(reservation))
  .otherwiseConsume(failed ->
      traveler.inform(ticketInfo, failed));

In the above example the Actor requesting reserveTravel() is the BookingActor that implements the Booking protocol. Inside andThenTo() the function sends record(reservation) to the Booking, which will eventually be received by the enclosing BookingActor. The BookingActor will thus receive thisrecord(reservation) message through its mailbox on a thread that has exclusive access to its state to effect the reservation recording.

There is another kind of failure, one that involves receiving an exception as a result. To deal with these, you use recoverFrom(). Consider this example:

reservations
  .reserveTravel(ticketInfo)
  .andThenTo(2_000, Failed.value(), reservation -> booking.record(reservation))
  .otherwiseConsume(failed -> traveler.inform(ticketInfo, failed))
  .recoverFrom(cause -> reservationsObserver.error(ticketInfo, cause));

In the above example, cascading failure is naturally avoided and recoverFrom() is used to report the cause of the exceptional problem to a component that can handle to problem using an alternate workflow.

Outcome

An Outcome is a result of an operation that may be either successful or a failure. If the resulting Outcome is a success then the client may respond in a positive manner, and if a failure in a recovery or compensating manner. Thus, there are two concrete types available, Success and Failure.

For example, a persistence operation may have a successful or failure outcome, depending on whether the expected persistence occurred or not. In such a contract, the outcome may be declared as the following type: Outcome<StorageException, Result>

The Outcome producer may provide one or the other, but not both. If successful, the behavior would produce something such as the following.

Success.of(Result.Success)

Or, if the requested behavior resulted in failure based on the Exception value of e, the following may be the product of the component.

Failure.of(new StorageException(Result.Failure, e.getMessage(), e))

Of course, the Outcome will have one value or the other, either StorageException or Result. How is it determined by the client and reacted to?

outcome
  .andThen(result -> {
    // do things based on success
    return result;
  })
  .otherwise(cause -> {
    // do things based on failure
    throw new StorageException(cause.result, message, cause);
  });

This is a familiar interface as seen in the Complete<T> API. When the operation is successful the andThen(f) operation is executed. When the operation is a failure the otherwise(f) operation is executed, with the Exception cause as the parameter. The otherwise(f) is not required to throw an Exception, but may answer the same or different Result value that is found in the cause.

There are additional behaviors available with Outcome, both for Success and Failure.

Scheduler

Every Stage has its own Scheduler, which may be used to produce time-lapsed events that are sent to an Actor.

// inside an actor
final DataPacket packet = new DataPacket(0);
stage().scheduler().schedule(selfAs(Scheduled.class), packet, 100, 1_000);

An Actor can schedule itself or another Actor, such as one or more of its children, for some timed event notification. In other words, it need not pass itself as the Scheduled instance.

The above example registers a repeating schedule that will begin within 100 milliseconds of the registration and will repeat every 1 second (1_000 milliseconds). The Actor scheduling itself for notifications must implement the Scheduled protocol, and it passes an Actor enabled reference to that effect using the runtime method selfAs(). The Actor can associate some specific data with which it will be notified on each event, which in this example is the DataPacket instance packet. This DataPacket type is only used for the example, and would be replaced with your own type, or you can pass null if the data is unused.

Similarly you can schedule a single notification. The interval will not not be repeated as in the above example.

// inside an actor
final DataPacket packet = new DataPacket(0);
stage().scheduler().scheduleOnce(selfAs(Scheduled.class), packet, 100, 1_000);

When registering a Scheduled object you are provided a Cancellable instance. You may use this instance to cancel one-time or repeating occurrences.

this.cancellable = stage().scheduler().scheduleOnce(...);
...
cancellable.cancel();

The Actor receiving the timed event will be notified using the intervalSignal() method of the Scheduled protocol.

public interface Scheduled {
  void intervalSignal(final Scheduled scheduled, final Object data);
}

It may be implemented something like the following.

@Override
public void intervalSignal(final Scheduled scheduled, final Object data) { 
  ((DataPacket) data).ifAccumulatedEnd(cancellable -> cancellable.cancel());
}

Serialization

Serializing objects to a format that can be sent over the network and/or persisted is an important feature of any distributed computing platform. The VLINGO XOOM platform will provide several different serialization capabilities. Currently we support only a single serialization option.

JSON Serialization

The JSON serializer is provided by class JsonSerialization. Internally this utility uses the com.google.gson.Gson serializer, which supports field-level access rather than requiring JavaBean accessor methods. The following operations are available.

Serializes the Object parameter to a String:

public static String serialized(final Object instance);

Serializes the List<T> parameter to a String:

public static <T> String serialized(final List<T> instance);

Deserializes the serialization parameter to an instance of Class<T>:

public static <T> T deserialized(String serialization, final Class<T> type);

Deserializes the serialization parameter to an instance of Type:

public static <T> T deserialized(String serialization, final Type type);

Deserializes the serialization parameter to an instance of a List of type Type:

public static <T> List<T> deserializedList(String serialization, final Type listOfType);

Tuples

A tuple is a data structure that collects two or more related values together, but without giving the group of values a type specific to their whole composition. By definition there is a tuple-0 and a tuple-1, but in such cases Java and other programming languages would typically use void and the single value type, respectively, to represent those tuples. Using a tuple type eliminates the need to create many tiny types just to shuttle around individual values of which it is unimportant to give a domain-specific type.

Our toolkit provides Tuple2, Tuple3, and Tuple4. Each of these tuple types support the specific number of values indicated by the trailing number of the name.

To get a Tuple2, use class method from() as follows:

final Tuple2<Integer,Integer> deux = Tuple2.from(4, 2);

To access the values of the tuple, use the public final instances directly:

System.out.println("Value 1: " + deux._1);
System.out.println("Value 2: " + deux._2);

Similarly with Tuple3 and Tuple4, you may use the following:

final Tuple3<Integer,Integer,String> trois = Tuple3.from(4, 2, "42");
System.out.println("Value 1: " + trois._1);
System.out.println("Value 2: " + trois._2);
System.out.println("Value 3: " + trois._3);

final Tuple4<Integer,Integer,String,String> quatre = Tuple4.from(4, 2, "42", "four");
System.out.println("Value 1: " + quatre._1);
System.out.println("Value 2: " + quatre._2);
System.out.println("Value 3: " + quatre._3);
System.out.println("Value 4: " + quatre._4);

Semantic Versions

Our toolkit supports semantic versioning, which defines a version with three parts: major, minor, and patch. Each part of the semantic version representation is separated by a dot (decimal point):

major.minor.patch

Each part of the version is a number, the following representing a major version of 1, a minor version of 7, and a patch version of 3:

1.7.3

Note that the major version may have a maximum value of 32_767, and the minor and patch values are limited to 255 each. The minimum value of all parts is 0.

Using our SemanticVersion utility tool, the representations supported are both packed integer and text format. These are the available operations.

Answers a new SemanticVersion instance with the given major, minor, and patch values:

public static SemanticVersion from(final int major, final int minor, final int patch);

Answers a new SemanticVersion instance with the given major, minor, and patch values as represented by the version parameter. The version parameter must have the representation "x.y.z", where x, y, and z, are numbers:

public static SemanticVersion from(final String version);

Answers a String representation of the semantic version represented by the packed version integer value:

public static String toString(final int version);

Answers a new integer value that is a packed representation of the given major, minor, and patch parameter values:

public static int toValue(final int major, final int minor, final int patch);

Answers a packed integer representation of the given major, minor, and patch values as represented by the version parameter. The version parameter must have the representation "x.y.z", where x, y, and z, are numbers:

public static int toValue(final String version);

The SemanticVersion instances provide the following behaviors.

Answers true if the receiver SemanticVersion is compatible with the previous SemanticVersion parameter:

public boolean isCompatibleWith(final SemanticVersion previous);

The two are compatible if one of these is true: (a) the major version of the receiver is one greater than the previous major version, and all other values are the same (b) the minor version of the receiver is one greater than the previous minor version, and all other values are the same (c) the patch version of the receiver is one greater than the previous patch version, and all other values are the same.

Answers a new SemanticVersion with its major version one greater than the receiver's, and all other values the same:

public SemanticVersion withIncrementedMajor();

Answers a new SemanticVersion with its minor version one greater than the receiver's, and all other values the same:

public SemanticVersion withIncrementedMinor();

Answers a new SemanticVersion with its patch version one greater than the receiver's, and all other values the same:

public SemanticVersion withIncrementedPatch();

Answers the receiver as its String representation:

public String toString();

Answers the receiver as its packed integer representation:

public int toValue();

Last updated