Invoking Server-side Methods on a Single Object
Invoking a method on the ScaleOut data grid involves calling the cache.singleInvoke method (or one of its synchronous variants). The method takes the following parameters:
key: The key to the persisted object in ScaleOut service that is to be evaluated by the server-side handler. To minimize network overhead, the event will be sent directly to the ScaleOut host that contains this persisted state object.
operationId: An optional, arbitrary string associated with the invocation. This string is typically used to identify the operation to invoke if more than one kind of operation is sent to and handled by the data grid. However, if the operation can be entirely described by a string (say, as a JSON-serialized object) then the operationId string can also serve as the payload of the event.
payload: An optional, arbitrary byte array containing the payload of an argument for the handler to use (typically a serialized object).
invokeTimeout: The amount of time allowed for the singleInvoke operation to complete on the server. Use, Duration.ZERO for an infinite timeout.
Example SMI
Consider an application that stores stock histories that can be queried for price values. In this sample, the following PriceHistory object would be defined in a shared project so that both client and server-side logic can use it.
package com.scaleout.client.samples.compute;
import java.util.HashMap;
public class PriceHistory {
private final String _stockTicker;
private final HashMap<Long,Double> _priceHistory;
public PriceHistory(String stockTicker, HashMap<Long,Double> currentPriceHistory) {
_stockTicker = stockTicker;
_priceHistory = currentPriceHistory;
}
public String getStockTicker() {
return _stockTicker;
}
public HashMap<Long, Double> getPriceHistory() {
return _priceHistory;
}
public void addToPriceHistory(long timestampMs, double price) {
_priceHistory.put(timestampMs, price);
}
}
A client would use a Cache instance to add any number of price history objects to the ScaleOut in-memory data grid. Here we add a single history object:
public static long addLargeHistoryObject() throws GridConnectException, CacheException {
// Return a random time to evaluate in the invoke call...
long timeToEvaluate = 0L;
// Create a large object with 10 years of price history.
int historyDays = 3650;
// Add fake data to ClosingPrices:
Random rand = new Random();
long twentyFourHoursMs = 1000 * 60 * 60 * 24;
long currentTimeMs = System.currentTimeMillis();
long midnightOffset = currentTimeMs % twentyFourHoursMs;
long historicDate = currentTimeMs - midnightOffset;
String stockTicker = "MSFT";
HashMap<Long, Double> generatedHistory = new HashMap<>();
int idxToEvaluate = rand.nextInt(historyDays);
for (int i = 0; i < historyDays; i++) {
historicDate = historicDate - twentyFourHoursMs;
generatedHistory.put(historicDate, 200.0d + rand.nextDouble());
timeToEvaluate = i == idxToEvaluate ? historicDate : 0L;
}
PriceHistory priceHistory = new PriceHistory(stockTicker, generatedHistory);
// Connect to Scaleout service
GridConnection connection = GridConnection.connect("bootstrapGateways=localhost");
Cache<String, PriceHistory> cache = new CacheBuilder<String, PriceHistory>(connection, "SMISample", String.class)
.build();
// Send this large object to the ScaleOut service:
CacheResponse<String,PriceHistory> addResponse = cache.addOrUpdate(priceHistory.getStockTicker(), priceHistory);
if(addResponse.getStatus() != RequestStatus.ObjectAdded || addResponse.getStatus() != RequestStatus.ObjectUpdated) {
throw new RuntimeException("Couldn't add object to ScaleOut cache.");
}
return timeToEvaluate;
}
Using singleInvoke
In this sample, the ScaleOut service is storing price history objects, keyed by ticker symbol. Rather than using the traditional method of moving the entire history object over the network to analyze it, we use singleInvoke to send a method call to the ScaleOut host that holds the stock’s history.
// Connect to the cache that stores login times.
GridConnection connection = GridConnection.connect("bootstrapGateways=localhost");
Cache<String, PriceHistory> cache = new CacheBuilder<String, PriceHistory>(connection, "SMISample", String.class)
.build();
// Add some test data to the cache:
long timeToEvaluate = addLargeHistoryObject();
InvokeResponse response = cache.singleInvoke("MSFT", "Historic price query", ByteBuffer.allocate(Long.BYTES).putLong(timeToEvaluate).array(), Duration.ofSeconds(0));
switch (response.getRequestStatus()) {
case InvokeComplete:
System.out.println("Invoke complete.");
if(response.getSuccessCount() == 1) {
System.out.println(ByteBuffer.wrap(response.getResultObject()).getDouble());
}
break;
case UnhandledExceptionInCallback:
System.out.println("Unhandled exception thrown from SMI Handler.");
System.out.println(new String(response.getErrorData(), StandardCharsets.UTF_8));
break;
default:
System.out.println("Unexpected response: " + response.getRequestStatus());
}
Unhandled Exceptions
Avoid throwing unhandled exceptions from server-side invocation callbacks whenever possible. In case an exception does escape from your handler, it will be sent back and made available to the singleInvoke caller through InvokeResponse.getErrorData()
. The representation and encoding of the error will vary depending on the library that is used to handle invoked events; if the handler is implemented using the Scaleout Client library, the error will be the full printStackTrace()
String representation of the exception, encoded as UTF-8.