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.
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.
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.
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.
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()
.
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?
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,
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:
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?
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
.
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.
When registering a Scheduled
object you are provided a Cancellable
instance. You may use this instance to cancel one-time or repeating occurrences.
The Actor
receiving the timed event will be notified using the intervalSignal()
method of the Scheduled
protocol.
It may be implemented something like the following.
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
:
Serializes the List<T>
parameter to a String
:
Deserializes the serialization
parameter to an instance of Class<T>
:
Deserializes the serialization
parameter to an instance of Type
:
Deserializes the serialization
parameter to an instance of a List
of type Type
:
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:
To access the values of the tuple, use the public final instances directly:
Similarly with Tuple3
and Tuple4
, you may use the following:
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):
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
:
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:
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:
Answers a String
representation of the semantic version represented by the packed version integer value:
Answers a new integer value that is a packed representation of the given major
, minor
, and patch
parameter values:
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:
The SemanticVersion
instances provide the following behaviors.
Answers true
if the receiver SemanticVersion
is compatible with the previous SemanticVersion
parameter:
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:
Answers a new SemanticVersion
with its minor
version one greater than the receiver's, and all other values the same:
Answers a new SemanticVersion
with its patch
version one greater than the receiver's, and all other values the same:
Answers the receiver as its String
representation:
Answers the receiver as its packed integer representation:
Last updated