Provides a distributed implementation of the classic Command Pattern (Wikipedia) built using Oracle Coherence.
The Command Pattern implementation is distributed in the jar file
called: coherence-commandpattern-11.0.0.jar
.
However as the Command Pattern has several other dependencies, we **strongly recommend** that developers adopt and use tools like Apache Maven/Ivy/Gradle to transitively resolve the said dependencies, instead of attempting to do so manually.
To configure your Apache Maven-based application to use the Command Pattern,
simply add the following declaration to your application pom.xml
file
<dependencies>
element.
<dependency>
<groupId>com.oracle.coherence.incubator</groupId>
<artifactId>coherence-commandpattern</artifactId>
<version>11.0.0</version>
</dependency>
The Command Pattern advocates that:
* An action to be performed zero or more times at some point in the future
should be represented as an object called a Command
.
All necessary parameters required to perform the said action, should be
encapsulated as attributes with in the said Command
.
The Command
must provide a mechanism to perform the action, typically
represented by an execute
method defined on the said Command
.
This implementation of the Command Pattern additionally advocates that:
* A Command
may only be executed with in the scope of a target object,
called a Context
To execute a Command
it must be submitted to a Context
Once submitted, Commands
are executed asynchronously. That is, the
submitter of the Command
does not block or wait for the Command
to be
executed after it has been submitted.
Commands
are executed one-at-a-time, in the order in which they arrive
at their Context
A Command
may not return a value to the application that submitted the
said Command
. If you require a Command
that returns a value after
execution, you need a Functor
(see the Functor Pattern.)
The following diagram outlines the core classes and interfaces defined in this implementation of the Command Pattern.
The Command
interface specifies the minimum requirements for Commands. It
defines a single method called execute
that is invoked by the internal
CommandExecutor
when a Command
is to be executed. The execute
method
requires a single parameter, called the ExecutionEnvironment
. The
ExecutionEnvironment
provides valuable information to the execute
method,
including the Context
in which the Command
is being executed. To submit
Commands
for execution by CommandExecutor
s, you must use an implementation
of the CommandSubmitter
interface (the DefaultCommandSubmitter
class
provides the standard implementation).
To define and register a Context
about which you may submit Command
s for
execution, use an implementation of the ContextsManager
interface (the
DefaultContextsManager
class provides the standard implementation). Once a
Context
is registered, you may use the returned Identifier
to submit Command
s
for execution with a CommandSubmitter
.
As
Context
andCommand
instances are stored in Coherence cluster members, they both must beSerializable
or better still, implementExternalizableLite
orPortableObject
.
The Client Application may be external to the Coherence Cluster when using Coherence Extend
Context
The client application registers the created Context
using a ContextsManager
The ContextsManager
places the Context
into a "primary partition" of the
"contexts" Distributed Cache.
Coherence ensures a backup of the Context
is made to a "backup partition"
(on separate JVMs and different machines where possible)
Context
is registered, an internal event establishes an appropriate
CommandExecutor
and necessary infrastructure.Command
The client application uses a CommandSubmitter
to submit a Command
for
execution with an identified Context
The submitted Command
is placed into the "commands" Distributed Cache
(and automatically backed up)
The submitted Command
is then automatically queued (FIFO) and scheduled
for asynchronous execution by the CommandExecutor
for the Context
An individual
Command
instance may be submitted any number of times for execution to one or moreContexts
. There is no need to create new instances of the sameCommand
if it needs to be submitted for execution multiple times.
Commands
may be submitted for execution at once.Commands
are queued for execution (FIFO) in order of arrival at the Context
When Commands
are queued for execution, an internal event notifies the
CommandExecutor
to start executing the Commands
For efficiency, the CommandExecutor
may execute Commands
in batches
(but remaining in order).
Command
has been executed, it is removed from the "commands" cache
(as is the backup of the Command
).Let's start with a simple example where we use the Command Pattern to setValue(T)
on the GenericContext
.
First, we'll start by writing a simple SetValueCommand
that extends `
AbstractCommand` as seen here:
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import com.oracle.coherence.patterns.command.Command;
import com.oracle.coherence.patterns.command.ExecutionEnvironment;
import com.tangosol.io.ExternalizableLite;
import com.tangosol.util.ExternalizableHelper;
@SuppressWarnings("serial")
public class SetValueCommand<T> implements Command<GenericContext<T>>, ExternalizableLite {
private T value;
public SetValueCommand() {
}
public SetValueCommand(T value) {
this.value = value;
}
public void execute(ExecutionEnvironment<GenericContext<T>> executionEnvironment) {
GenericContext<T> context = executionEnvironment.getContext();
context.setValue(value);
executionEnvironment.setContext(context);
}
@SuppressWarnings("unchecked")
public void readExternal(DataInput in) throws IOException {
this.value = (T)ExternalizableHelper.readObject(in);
}
public void writeExternal(DataOutput out) throws IOException {
ExternalizableHelper.writeObject(out, value);
}
public String toString() {
return String.format("SetValueCommand{value=%s}", value);
}
}
Second, let's write a quick test that submits a number (iCount
) of
SetValueCommand
s on "mycontext" as seen here:
import com.oracle.coherence.common.identifiers.Identifier;
import com.oracle.coherence.patterns.command.CommandSubmitter;
import com.oracle.coherence.patterns.command.ContextsManager;
import com.oracle.coherence.patterns.command.DefaultCommandSubmitter;
import com.oracle.coherence.patterns.command.DefaultContextConfiguration;
import com.oracle.coherence.patterns.command.DefaultContextsManager;
import com.oracle.coherence.patterns.command.ContextConfiguration.ManagementStrategy;
import com.tangosol.net.CacheFactory;
public class CommandPatternExample {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
DefaultContextConfiguration contextConfiguration = new DefaultContextConfiguration(ManagementStrategy.DISTRIBUTED);
ContextsManager contextsManager = DefaultContextsManager.getInstance();
Identifier contextIdentifier = contextsManager.registerContext("mycontext", new GenericContext<Long>(0L), contextConfiguration);
CommandSubmitter commandSubmitter = DefaultCommandSubmitter.getInstance();
commandSubmitter.submitCommand(contextIdentifier, new LoggingCommand("Commenced", 0));
for (long i = 1; i <= 10000; i++) {
commandSubmitter.submitCommand(contextIdentifier, new SetValueCommand<Long>(i));
}
commandSubmitter.submitCommand(contextIdentifier, new LoggingCommand("Completed", 0));
CacheFactory.shutdown();
}
}
To run this test you will need to start a Coherence cache server, in this example
we use the DefaultCacheServer
that ships with Coherence as seen here:
java -Dtangosol.coherence.cacheconfig=$PATH_TO/coherence-commandpattern-cacheconfig.xml \
com.tangosol.net.DefaultCacheServer
Once the cache server is running you can start the CommandPatternExample
as a
cache client as seen here:
java -Dtangosol.coherence.distributed.localstorage=false -Dtangosol.coherence.cacheconfig=$PATH_TO/coherence-commandpattern-cacheconfig.xml CommandPatternExample
The SetValueCommand.java
, CommandPatternExample.java
and coherence-commandpattern-cache-config.xml
files are included in the source code distribution.
The following table outlines the main differences.
Description | Functor Pattern | Command Pattern | EntryProcessors | InvocationService |
Processing Target | A Context | A Context | A Cache Entry | A Cluster Member |
Submission Behavior | Non-Blocking | Non-Blocking | Blocking | Blocking and Non-Blocking |
Execution Order | Order of Submission | Order of Submission | Order of Submission^1 | Order of Submission |
Supports Return Values? | Yes | No | Yes | Yes |
Guaranteed to Execute?^2 | Yes | Yes | Yes^3 | Yes^4 and No^5 |
Guaranteed to Execute?^6 | Yes | Yes | Yes^7 | No^8 |
Recoverable?^9 | Yes^10 | Yes^10 | No | No |
Request may be cancelled? | Yes | Yes | No | Yes ^5 |
A ManagementStrategy
defines how Commands
are managed in a Coherence Cluster.
By default Commands
are always co-located in the JVM that is hosting the Context
associated with the said Commands
. This provides instant access to the Commands
for a CommandExecutor
but has the disadvantage of limiting the number of Commands
that may be queued for execution (before a JVM may run out of memory). The alternative
is to use the DISTRIBUTED
strategy, where the Commands
are distributed across
the cluster and only retrieved for execution when required. This provides the
advantage of enabling massive capacity (scaling to the size of the cluster and
beyond when disk storage is configured), but has the disadvantage that retrieving
Commands
for execution may take longer.
The Command Pattern CommandExecutors
are JMX enabled by default. In order
to monitor Command
execution for a Context
, simply enable JMX for Coherence
and look for the "CommandExecutors" in your JMX monitoring application
(ie: Something like JConsole). All kinds of JMX information is available,
including Min, Max and Average Execution time, together with the number of
current Commands
pending to be executed and those that have been executed.
Wikipedia - Command Pattern