Common Tools
Some common tools reused by multiple VLINGO XOOM platform components.
This is an overview of the more important tools found in XOOM Common.
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.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
.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());
}
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.
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);
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);
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 modified 10mo ago