Java SDK
Ice Java SDK (ice-core) provides the rule execution engine and file client for integration into Java/Spring applications.
Dependency
<dependency>
<groupId>com.waitmoon.ice</groupId>
<artifactId>ice-core</artifactId>
<version>3.0.2</version>
</dependency>Requires Java 8+.
Initializing the Client
IceFileClient client = new IceFileClient(
1, // app ID
"./ice-data", // shared storage path (must be the same directory as Server)
"com.your.package" // leaf node scan package
);
client.start();Full parameter version:
IceFileClient client = new IceFileClient(
1, // app ID
"./ice-data", // shared storage path
-1, // parallelism (<=0 uses default ForkJoinPool)
Set.of("com.your.package"), // scan package set
5, // version poll interval (seconds)
10 // heartbeat interval (seconds)
);With lane:
IceFileClient client = IceFileClient.newWithLane(
1, "./ice-data", "com.your.package", "feature-xxx"
);See Client Configuration Reference for full parameter documentation.
Spring Integration
Spring/SpringBoot projects need to bridge the Spring container to Ice so that leaf nodes can use @Resource / @Autowired to inject Beans:
@Configuration
public class IceConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext ctx) {
AutowireCapableBeanFactory bf = ctx.getAutowireCapableBeanFactory();
IceBeanUtils.setFactory(new IceBeanUtils.IceBeanFactory() {
@Override
public void autowireBean(Object bean) { bf.autowireBean(bean); }
@Override
public boolean containsBean(String name) { return ctx.containsBean(name); }
@Override
public Object getBean(String name) { return ctx.getBean(name); }
});
}
@Bean(destroyMethod = "destroy")
public IceFileClient iceFileClient() throws Exception {
IceFileClient client = new IceFileClient(1, "./ice-data", "com.your.package");
client.start();
return client;
}
}Non-Spring projects do not need this step.
Leaf Node Development
Three Node Types
| Type | Base Class | Return Value | Purpose |
|---|---|---|---|
| Flow | BaseLeafRoamFlow | true / false | Conditional checks |
| Result | BaseLeafRoamResult | true / false | Business execution |
| None | BaseLeafRoamNone | none | Auxiliary operations |
Flow Node Example
@Data
@EqualsAndHashCode(callSuper = true)
public class ScoreFlow extends BaseLeafRoamFlow {
private String key;
private double score;
@Override
protected boolean doRoamFlow(IceRoam roam) {
Double value = roam.getMulti(key);
return value != null && value >= score;
}
}Result Node Example
@Data
@EqualsAndHashCode(callSuper = true)
public class AmountResult extends BaseLeafRoamResult {
@Resource
private SendService sendService;
private String key;
private double value;
@Override
protected boolean doRoamResult(IceRoam roam) {
Integer uid = roam.getMulti(key);
if (uid == null || value <= 0) {
return false;
}
boolean res = sendService.sendAmount(uid, value);
roam.put("SEND_AMOUNT", res);
return res;
}
}None Node Example
@Data
@EqualsAndHashCode(callSuper = true)
public class TimeChangeNone extends BaseLeafRoamNone {
private long changeTime;
@Override
protected void doRoamNone(IceRoam roam) {
roam.put("requestTime", changeTime);
}
}Base Class Selection
Ice provides three levels of base classes. Choose the appropriate input granularity:
| Base Class | Input | Use Case |
|---|---|---|
BaseLeafFlow / BaseLeafResult / BaseLeafNone | IceContext | Need full context |
BaseLeafPackFlow / BaseLeafPackResult / BaseLeafPackNone | IcePack | Need requestTime, traceId |
BaseLeafRoamFlow / BaseLeafRoamResult / BaseLeafRoamNone | IceRoam | Only need to read/write data (most common) |
Node Annotations
@IceNode(
name = "Amount Dispenser",
desc = "Dispense a specified amount to the user",
order = 10,
alias = {"amount_result"} // Alias for compatibility with rules configured by other language SDKs
)
public class AmountResult extends BaseLeafRoamResult {
@IceField(name = "User Key", desc = "Key to retrieve user ID from roam")
private String key;
@IceField(name = "Dispense Amount", desc = "The amount to dispense")
private double value;
@IceIgnore // Hidden from the configuration interface
private String internalField;
}Executing Rules
Assembling a Pack
IcePack pack = new IcePack();
pack.setIceId(1L); // Trigger by iceId
// pack.setScene("recharge"); // Or trigger by scene
// pack.setConfId(123L); // Or trigger by node ID
IceRoam roam = new IceRoam();
roam.put("uid", 12345);
roam.put("cost", 100);
pack.setRoam(roam);Trigger priority: iceId > scene > confId.
Synchronous Execution
Ice.syncProcess(pack);
// Retrieve results from roam after execution
Object result = pack.getRoam().get("SEND_AMOUNT");Asynchronous Execution
List<Future<IceContext>> futures = Ice.asyncProcess(pack);
for (Future<IceContext> future : futures) {
IceContext ctx = future.get();
// Process result
}Convenience Methods
IceRoam roam = Ice.processSingleRoam(pack); // Single result Roam
List<IceRoam> roams = Ice.processRoam(pack); // Multiple result Roams
IceContext ctx = Ice.processSingleCtx(pack); // Single result Context
List<IceContext> ctxList = Ice.processCtx(pack); // Multiple result ContextsError Handling
Three approaches, in descending order of priority:
1. Configure Error State
Set the node's iceErrorStateEnum in the Server configuration interface to specify the return state on error. This has the highest priority.
2. Override errorHandle in Leaf Nodes
@Override
public NodeRunStateEnum errorHandle(IceContext ctx, Throwable t) {
log.error("Node execution failed", t);
return NodeRunStateEnum.NONE; // Return NONE to continue execution
// return NodeRunStateEnum.SHUT_DOWN; // Terminate the entire flow
}3. Global Error Handler
IceErrorHandle.setHandle((node, ctx, t) -> {
log.error("Global error handler node:{}", node.getIceNodeId(), t);
return NodeRunStateEnum.SHUT_DOWN;
});Integration with Expression Engines
Ice can integrate with expression engines like Aviator to simplify condition configuration:
@Data
@EqualsAndHashCode(callSuper = true)
public class AviatorFlow extends BaseLeafRoamFlow {
private String exp;
private Expression compiledExpression;
@Override
protected boolean doRoamFlow(IceRoam roam) {
return (boolean) compiledExpression.execute(roam);
}
public void setExp(String exp) {
this.exp = exp;
this.compiledExpression = AviatorEvaluator.compile(exp, true);
}
}Configure exp as an Aviator expression (e.g., cost >= 100) to dynamically modify evaluation conditions in the Server interface.
Next Steps
- Go SDK | Python SDK -- Other language SDKs
- Node Type Reference -- All relation and leaf node types
- Roam API -- Complete data container API
- Core Concepts -- Understand tree-based orchestration design philosophy