RoutingExample.java
Divide and Conquer:
In the pursuit of colocation and affinity the architect/developer must articulate
what the criteria is for routing the information in the system to the best place.
With GigaSpaces, this means establishing a routing value that will instruct the
proxy how to route the instance of the object in question.
Here I propose a pattern to direct the routing value and allow for increased
topology transparency within the deployment environment.
Firstly: it is important to specify a dedicated property rather than reuse an
existing one as the runtime routing value needs to be flexible.
Secondly: many times utilizing a primitive is desired by the designer of a system
and once this is done, a null-equivalent is required to allow for wide-ranging
queries accross multiple partitions. I take the opportunity here to specify a
common routing value for numerics expressed initially as a String to be parsed as
a float or int or double etc by the associated java.lang.Float etc… type.
The pattern specifies the use of a Routable interface (which must be implemented
by any Object seeking to be routed) and a SpaceRoutingHelper utility that is clever
enough to route Routables to the appropriate partition without change of code ie:
‘transparently’ regardless of whether there is one or 99 partitions in the Space.
Here is the Routable interface:
package gslabs.util;
/**
* Created by IntelliJ IDEA.
* User: owen
* Date: 5-Jun-2007
* Time: 12:25:12 PM
*/
public interface Routable {
public static final String DEFAULT_NUMERIC_NULL_VALUE=“-9999”;
public void setRoutingFieldValue(int val);
public int getRoutingFieldValue();
}
Here is the SpaceRoutingHelper:
package gslabs.util;
import com.j_spaces.core.IJSpace;
import com.j_spaces.core.cluster.ClusterPolicy;
import com.j_spaces.core.admin.IRemoteJSpaceAdmin;
/**
* Created by IntelliJ IDEA.
* User: owen
* Date: 5-Jun-2007
* Time: 12:09:12 PM
*/
public class SpaceRoutingHelper {
IJSpace space = null;
public int NUMBER_PARTITIONS = 1;
/**
* @param val The Routable Object to be used with a partitioned Space
* @param seed The provided value that will determine the target partition (if left null, target partition is
* determined randomly)
* @return The prepared version of the Routable Object with the routingField specified
*/
public Routable prepareRoutable(Routable val, Object seed) {
if (space == null) {
throw new IllegalStateException(“Cannot Route Routable: target space = null”);
}
int router = 0;
if (seed == null) {
router = (int) System.nanoTime() % NUMBER_PARTITIONS;
} else {
router = seed.hashCode() % NUMBER_PARTITIONS;
}
val.setRoutingFieldValue(router);
return val;
}
public void setSpace(IJSpace space) {
this.space = space;
if (space != null) {
try {
IRemoteJSpaceAdmin spaceAdmin = (IRemoteJSpaceAdmin) space.getAdmin();
ClusterPolicy cpolicy = spaceAdmin.getClusterPolicy();
if(cpolicy!=null){
java.util.List l = cpolicy.m_AllClusterMemberList;
if (l.size() > 1) {
int partitionValue = 1;
try {
partitionValue = cpolicy.m_ReplicationGroups.size();
if (partitionValue == 0) {
//no replicationGroups, so #partitions == size of space names
partitionValue = l.size();
}
} catch (NullPointerException npe) {
//no replicationGroups, so #partitions == size of space names
partitionValue = l.size();
}
NUMBER_PARTITIONS = partitionValue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Example:
The following service writes Offers to the space. In doing so the decision of
how to route the various instances is made using the SpaceRoutingHelper. This
means that without changing the code, the application can be deployed against a
cluster of any number of spaces or a single embedded space.
Additionally, I will show the use of the Routable.DEFAULT_NUMERIC_NULL_VALUE.
****
package os.example.trading;
import org.openspaces.events.adapter.SpaceDataEvent;
import org.openspaces.core.GigaSpace;
import os.example.trading.common;
import java.math.BigDecimal;
import gslabs.util.SpaceRoutingHelper;
/**
* <p>A simple bean that is run within a Spring context that acts
* as the processing unit context as well (when executed within a
* processing unit container).
*
* <p>Is handed SpaceDataEvents from a polling container.<br />
* This removes the need to use space.take and space.write in the code.
*
* <p>The SQLQuery for the polling container is defined in the pu.xml.
*/
public class MarketMakerWorker {
gsutil.SpaceRoutingHelper router = new SpaceRoutingHelper();
private float lastPrice_NASD,lastPrice_NYSE,lastPrice_AMEX;
private GigaSpace space;
private float highPrice, lowPrice;
public MarketMakerWorker(GigaSpace space, float highPrice, float lowPrice){
this.space = space;
this.highPrice = highPrice;
this.lowPrice = lowPrice;
/*
IT IS NECESSARY TO PREPARE THE SpaceRoutingHelper by passing it a
reference to the Space. Using this reference it determines the total
number of partitions and routes the Routables properly.
*/
router.setSpace(space.getSpace());
}
/**
* This method is called any time the matching event is produced by the space.
* <p>The OpenSpaces PollingEventContainer will automagically call this method.
* @param data The data source of the event defined for this bean.
* In this example, the Stock object is evidence of a change in the market
* price for this instrument.
* [The return value is written to the space by the Spring container]
*/
@SpaceDataEvent
public Offer dataProcessed( Stock data) {
Offer offer = null;
if((data.getCurrentPrice().compareTo(new BigDecimal(highPrice))>0)){
offer =new Offer();
//bogus code used to set various criteria for demo purposes…
offer.setNumberOfShares(((System.nanoTime()%50)*1000)+500);
offer.setOfferID(“”+new java.sql.Date(
System.currentTimeMillis())+“_”+data.getSymbol()+“_sell”);
offer.setPrice(data.getCurrentPrice());
offer.setSymbol(data.getSymbol());
offer.setSell(Offer.IS_SELL_TRUE);
/*
NOTICE THE USE OF THE SpaceRoutingHelper and the decision to
Specify an affinity between the (data) Stock object and the
resulting Offer object. If desired, volume of shares could be
used in conjunction with Stock symbol to provide a finer-grained
partitioning strategy.
*/
offer = (Offer) router.prepareRoutable(
offer,new Integer(data.getRoutingFieldValue()));
}else if((data.getCurrentPrice().compareTo(
new BigDecimal(lowPrice))<0)){
offer = new Offer();
//bogus code used to set various criteria for demo purposes…
offer.setNumberOfShares(((System.nanoTime()%50)*1000)+500);
offer.setOfferID(“”+new java.sql.Date(System.currentTimeMillis())+“_”+data.getSymbol()+“_sell”);
offer.setPrice(data.getCurrentPrice());
offer.setSymbol(data.getSymbol());
offer.setSell(Offer.IS_SELL_FALSE);
/*
NOTICE THE USE OF THE SpaceRoutingHelper and the decision to
Specify an affinity between the (data) Stock object and the
resulting Offer object. If desired, volume of shares could be
used in conjunction with Stock symbol to provide a finer-grained
partitioning strategy.
*/
offer = (Offer) router.prepareRoutable(offer,new Integer(data.getRoutingFieldValue()));
}
return offer;
}
public float getLastAMEXPrice(){
return lastPrice_AMEX;
}
public float getLastNASDPrice(){
return lastPrice_NASD;
}
public float getLastNYSEPrice(){
return lastPrice_NYSE;
}
}
The following class definition requires several OpenSpaces-specific annotations
to play nicely with that framework – these will be demonstrated – in addition, I
will show the placement of the routingFieldValue and the exposure of several
primitive fields.
package os.example.trading.common;
import com.gigaspaces.annotation.pojo.SpaceId;
import com.gigaspaces.annotation.pojo.SpaceProperty;
import com.gigaspaces.annotation.pojo.SpaceRouting;
import java.math.BigDecimal;
/**
* Created by IntelliJ IDEA.
* User: owen
* Date: Oct 9, 2007
* Time: 3:46:00 PM
*/
public class Offer implements gslabs.util.Routable{
private String offerID;
private String symbol;
private long numberOfShares;
private BigDecimal price;
private int isSell;
public static final int IS_SELL_FALSE=0;
public static final int IS_SELL_TRUE=1;
private int routingFieldValue;
public void setRoutingFieldValue(int val) {
routingFieldValue = val;
}
@SpaceProperty(nullValue=Routable.DEFAULT_NUMERIC_NULL_VALUE)
@SpaceRouting
public int getRoutingFieldValue() {
return routingFieldValue;
}
@SpaceId
public String getOfferID() {
return offerID;
}
public void setOfferID(String offerID) {
this.offerID = offerID;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
@SpaceProperty(nullValue=Routable.DEFAULT_NUMERIC_NULL_VALUE)
public long getNumberOfShares() {
return numberOfShares;
}
public void setNumberOfShares(long numberOfShares) {
this.numberOfShares = numberOfShares;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
@SpaceProperty(nullValue=Routable.DEFAULT_NUMERIC_NULL_VALUE)
public int getSell() {
return isSell;
}
public void setSell(int sell) {
isSell = sell;
}
}