Core Concepts
Ice's core philosophy: organize business rules as tree structures where each node handles only its own logic, passing information through data wrappers (Roam) to achieve true business decoupling.
Why Tree Structures
In real-world business scenarios, the biggest challenge for rule engines is not "can it run" but rather "what happens when rules change."
Pain points of traditional approaches:
- Flowchart-based (Activiti / Flowable): Modifying one node requires rewiring connections, causing ripple effects throughout
- When-Then (Drools): Implicit dependencies between rules; changing one may affect others
- Hardcoded: Flexible but high maintenance cost; changing rules means changing code and redeploying
Ice's approach: Organize business rules as tree structures where each node is independently responsible for a single piece of logic. Nodes do not reference each other directly but pass information through data wrappers (Roam). Modifying any node only affects that node itself, with no need to worry about upstream or downstream impacts.
Example: Recharge Campaign
Company X's New Year recharge campaign:
- Recharge 100 yuan, get 5 yuan bonus balance (Jan 1 - Jan 7)
- Recharge 50 yuan, get 10 bonus points (Jan 5 - Jan 7)
- Rewards are not stackable
Decomposed business modules:


When a user recharges, a data Pack is created containing uid, cost, requestTime, and other information. Each module reads data from the pack, processes its logic, and writes results back.
Problems with Flowchart Approach


It looks clean, but once business requirements change -- removing the non-stackable restriction, adding inventory control, adjusting time windows -- you need to rearrange connections and evaluation order, where a single change requires considering all upstream and downstream effects.
Ice's Tree-Based Orchestration


Rules are organized using tree structures. Execution starts from the root and traverses child nodes in order. Relation nodes control flow logic, while leaf nodes execute business logic.
When rules change:
- Change amount threshold: Directly modify the configuration of the corresponding leaf node
- Remove non-stackable restriction: Change the root's ANY to ALL (the stacking logic only lives on this node)
- Add inventory control: No changes needed -- if dispensing fails and returns false, the flow automatically continues to the next node
This is the core advantage of tree-based orchestration: each node is responsible only for its own logic, and changes do not propagate.
Relation Nodes
Relation nodes control the execution flow of child nodes. There are 5 types, each with both serial and parallel versions.
Serial Relation Nodes
| Type | Execution | Return Logic | Analogy |
|---|---|---|---|
| AND | Sequential, stops on false | All true returns true; any false returns false | Java && |
| ANY | Sequential, stops on true | Any true returns true; all false returns false | Java || |
| ALL | Executes all, no short-circuit | Has true and no false returns true; any false returns false | |
| NONE | Executes all | Always returns none | |
| TRUE | Executes all | Always returns true (returns true even with no children) |
Key Distinction
AND/ANY use short-circuit execution, stopping immediately once the result is determined. ALL/NONE/TRUE execute all child nodes. Your choice of relation node depends on whether you need all child nodes to be executed.
Parallel Relation Nodes
Each serial relation node has a corresponding parallel version (ParallelAnd, ParallelAny, etc.) that submits child nodes to a thread pool or goroutine pool for concurrent execution.
- ParallelAnd / ParallelAny: Support early termination, returning immediately once the result is determined
- ParallelAll / ParallelNone / ParallelTrue: Wait for all child nodes to complete
Suitable for scenarios where child nodes have no data dependencies and involve I/O operations.
Leaf Nodes
Leaf nodes are where business logic is actually executed. There are three types:
| Type | Return Value | Purpose | Examples |
|---|---|---|---|
| Flow | true / false | Conditional checks that control flow direction | Amount validation, level checks, time filtering |
| Result | true / false | Execute business operations and return results | Issue coupons, deduct inventory, send notifications |
| None | none | Auxiliary operations that don't affect flow | Query user info, logging, data assembly |
Developing Leaf Nodes
Extend the corresponding base class and implement the business method. Fields are automatically mapped to Server configuration options:
@Data
@EqualsAndHashCode(callSuper = true)
public class AmountResult extends BaseLeafRoamResult {
private String key; // Config option: key for user ID
private double value; // Config option: amount to dispense
@Override
protected boolean doRoamResult(IceRoam roam) {
Integer uid = roam.getMulti(key);
if (uid == null || value <= 0) {
return false;
}
return sendService.sendAmount(uid, value);
}
}Ice provides three levels of base class abstraction. Choose the appropriate input granularity:
| Base Class Level | Input | Use Case |
|---|---|---|
BaseLeaf* | IceContext | Need access to full context (iceId, processInfo, etc.) |
BaseLeafPack* | IcePack | Need access to requestTime, traceId, and other pack info |
BaseLeafRoam* | IceRoam | Only need to read/write business data (most common) |
Data Model
Pack (Data Wrapper)
Pack is the input for each rule execution, containing trigger information and business data:
| Field | Type | Description |
|---|---|---|
iceId | long | ID of the rule to trigger |
scene | String | Scene name to trigger; all rules subscribed to this scene will execute |
confId | long | Execute with the specified node as root |
roam | IceRoam | Business data container |
requestTime | long | Request timestamp (affects node time window evaluation) |
traceId | String | Trace ID for distributed tracing, auto-generated |
debug | byte | Debug log level (bitmask) |
Trigger priority: iceId > scene > confId.
Roam (Data Container)
Roam is the core container for passing data between nodes. It is based on ConcurrentHashMap (thread-safe) and supports multi-level keys and reference syntax:
// Basic read/write
roam.put("uid", 12345);
roam.getValue("uid"); // 12345
// Multi-level key (automatically builds nested structure)
roam.putMulti("user.level", 5); // {"user": {"level": 5}}
roam.getMulti("user.level"); // 5
// Reference syntax ("@" prefix fetches value from roam)
roam.getUnion("@uid"); // 12345 (fetches the value of uid from roam)
roam.getUnion("hello"); // "hello" (non-@ prefix returns the raw value)Forward Nodes
Forward nodes are a mechanism to simplify configuration. When a relation node and a condition node always appear as a pair (AND-bound), the condition node can be set as a forward node of the target node:


Semantically equivalent to an AND connection, but reduces tree depth for cleaner configuration. When the forward node returns false, the main node will not execute.
Common Node Capabilities
Every node (whether relation or leaf) supports the following configurations:
| Configuration | Description |
|---|---|
| Time Window | Sets the effective time range for a node. Nodes outside the time window are treated as non-existent |
| Inverse | Inverts the node's true result to false and vice versa (NONE is not affected) |
| Error Handling | Behavior when a node throws an error: terminate the flow (default) or return a specified state to continue |
| Node Reuse | The same node ID can appear in multiple trees; modifying it once takes effect everywhere |
Time Windows and Testing
Time window configuration makes nodes automatically inactive outside specified time periods. Combined with a None-type time modification node, you can easily test rules that only take effect in the future:


Insert a TimeChangeNone node to modify the requestTime in the pack, and subsequent nodes will execute based on the modified time -- no need to wait for the campaign to actually begin.
Next Steps
- Getting Started -- Deploy and run your first rule in 5 minutes
- Architecture -- Understand how Server + Client + shared storage works
- Java SDK | Go SDK | Python SDK -- Integration guides for each language
- Node Type Reference -- Complete behavior reference for all relation and leaf nodes
Video Tutorial
Full video tutorial: Bilibili