Ice Rule Engine Detailed Guide

Comprehensive guide to mastering visual business orchestration with Ice rule engine

Rule Engine Node Development

Ice rule engine uses node-based design where each node represents an independent business logic unit. By combining different node types, you can implement complex business rule orchestration.

Three Leaf Node Types

Ice rule engine provides three leaf node types for different business scenarios:

1. Flow Node - Process Control

  • Purpose: Control business flow in the rule engine
  • Scenarios: Condition judgment, filtering rules, permission validation
  • Return Value: Clear true (pass) or false (fail)
  • Examples: User level judgment, amount range validation, time condition filtering

2. Result Node - Result Processing

  • Purpose: Execute specific business operations in the rule engine
  • Scenarios: Reward distribution, inventory deduction, send notifications
  • Return Value: true (execution success) or false (execution failure)
  • Examples: Coupon distribution, points reward, balance recharge

3. None Node - Auxiliary Operations

  • Purpose: Auxiliary nodes that don't affect business flow
  • Scenarios: Data query, logging, information assembly
  • Return Value: No return value (none)
  • Examples: User info query, event tracking, cache warming

Node Annotations

Ice 2.0 provides node annotations to enrich node metadata:

@IceNode(
    name = "Amount Reward",
    desc = "Send specified amount to user",
    order = 10  // Sort priority, smaller value means higher priority
)
public class AmountResult extends BaseLeafRoamResult {
    // ...
}

Field Annotations

@Data
public class AmountResult extends BaseLeafRoamResult {

    @IceField(name = "User Key", desc = "Key to get user ID from roam")
    private String key;

    @IceField(name = "Amount", desc = "Amount value to send", type = "double")
    private double value;

    @IceIgnore  // Ignore this field, not shown in configuration UI
    private String internalField;
}

Node Base Class Selection

Ice rule engine provides three base classes for developers to inherit, choose based on parameter type:

  • BaseLeaf* - Uses IceContext as method parameter, implement do* method
  • BaseLeafPack* - Uses IcePack as method parameter, implement doPack* method
  • BaseLeafRoam* - Uses IceRoam as method parameter, implement doRoam* method

Example:

/**
 * Issue balance node
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class AmountResult extends BaseLeafRoamResult { // The uid to send balance is obtained from roam

    @Resource
    private SendService sendService; // For Spring applications, use spring beans directly. For non-Spring applications, initialize IceBeanFactory in IceBeanUtils

    private String key; // Configurable uidKey

    private double value; // Configurable balance value

    @Override
    protected boolean doRoamResult(IceRoam roam) { // Implement doRoamResult (your business logic)
        Integer uid = roam.getMulti(key); // Get the uid from roam
        if (uid == null || value <= 0) {
            return false;
        }
        boolean res = sendService.sendAmount(uid, value); // Call third-party API to send balance
        roam.put("SEND_AMOUNT", res); // Put result back to roam for subsequent use
        return res; // Return result
    }
}

Execute Ice

Assemble IcePack

pack is the package that needs to be assembled before executing ice

  • iceId The ID of the rule to trigger, corresponds to backend configuration ID, iceId can only trigger one configured rule
  • scene The scene to trigger, all rules subscribed to this scene will be triggered
  • confId Trigger rule with any node ID as root
  • requestTime Request time, default System.currentTimeMillis()
  • roam Put the parameters and other information required to execute the rule
  • traceId Trace ID, automatically generated by default
  • debug Log printing, refer to DebugEnum. The final debug executed is handler.debug|pack.debug

Call the Ice method

  • void syncProcess(IcePack pack) Execute synchronously
  • List<Future<IceContext>> asyncProcess(IcePack pack) Execute asynchronously and return futures

In business, execution results may be added to roam, and desired data can be obtained from roam after execution.

IceRoam

Roam provides the data source required for node execution or stores execution results for subsequent execution. Roam is an extension of ConcurrentHashMap.

  • put/get Rewrites ConcurrentHashMap's put/get, ignoring null pointer exceptions
  • putValue/getValue Ignores type matching checks, saves casting operations, be careful about type matching
  • putMulti/getMulti Use "." to separate and build hierarchical data structures
  • getUnion If the parameter starts with "@", gets data from roam, otherwise returns the parameter itself
roam.putValue("a", 1); //{"a":1}
roam.getValue("a"); //1
roam.putMulti("b.c", 2); //{"a":1,"b":{"c":2}}
roam.putMulti("b.d", 3); //{"a":1,"b":{"c":2,"d":3}}
roam.getMutli("b"); //{"c":2,"d":3}
roam.getMutli("b.c"); //2
roam.getUnion("a"); //"a"
roam.getUnion("@a"); //1
roam.getUnion(1); //1
roam.put("e", "@a");
roam.getUnion("@e");//1
roam.put("e", "a");
roam.getUnion("@e");//"a"

Backend Configuration

app

app is used to distinguish different applications. For example, ice-test with app=1, when ice-test starts, it will pull all configurations for app 1 from the file system according to the configuration

Add ice

  • ID iceId can be triggered by iceId
  • Name Description
  • Scene Subscribed scenes, use "," to separate multiple scenes, triggered when any scene occurs
  • Configuration ID Root node ID of the ice tree
  • Debug Log printing, refer to DebugEnum, accumulate values for content to print
  • Operations
    • Edit Edit ice
    • View Details View detailed node configuration
    • Backup Backup configuration
    • Backup History Can restore from historical backups
    • Export Export current configuration (including unpublished changes)

Configure Node

Click a node to pop up related operations

  • View/Edit Nodes
  • Add Child Nodes Only for relationship nodes
  • Add Pre-Node Add pre-execution node
  • Convert Node Can convert current node to any node
  • Move Node Up/Down Move node
  • Delete This Node Node deletion is soft delete, just disconnects, not physically deleted, can be added back by node ID

Other Configuration:

  • confName Class name of leaf node. First time adding needs full class name, will validate if class exists in client
  • Node ID Adding child nodes by node ID is object-level reusability. Nodes with same ID only have one copy in memory
  • record Node's debug is only for display in processInfo
  • inverse Invert node, if node should return false, returns true after inversion

Publish, Clear, Import, Export

  • Publish All changes are only hot updated to client after publishing, unpublished changes have "^" mark
  • Clear Clear all changes, revert to last published version
  • Import Import configuration
  • Export Export current configuration (including unpublished changes)
  • Instance Selection
    • Server Server configuration, currently only Server mode supports editing
    • Client:host/app/uniqueId Real configuration display in client, only supports viewing

Storage Architecture (2.0 New Feature)

Ice 2.0 uses file system storage, completely removing MySQL dependency.

Directory Structure

ice-data/
├── apps/                    # Application configurations
│   ├── _id.txt             # Application ID generator
│   └── {app}.json          # Application configuration file
├── clients/                 # Client information
│   └── {app}/              # Grouped by application
│       ├── {address}.json  # Client heartbeat file
│       └── _latest.json    # Latest client info (O(1) read)
└── {app}/                   # Application rule configurations
    ├── version.txt         # Current version number
    ├── _base_id.txt        # Base ID generator
    ├── _conf_id.txt        # Conf ID generator
    ├── _push_id.txt        # Push ID generator
    ├── bases/              # Base rule configurations
    │   └── {baseId}.json
    ├── confs/              # Conf node configurations
    │   └── {confId}.json
    ├── updates/            # Pending changes
    │   └── {iceId}/
    │       └── {confId}.json
    ├── versions/           # Version incremental updates
    │   └── {version}_upd.json
    └── history/            # Publish history
        └── {pushId}.json

Version Sync Mechanism

  1. Server Publishes Configuration:

    • Updates configuration files in bases/ and confs/ directories
    • Generates incremental update file in versions/{version}_upd.json
    • Updates version.txt version number
  2. Client Polls for Updates:

    • Periodically checks version.txt version number
    • Reads incremental update file when new version is found
    • Falls back to full load if incremental file is missing
  3. Heartbeat Mechanism:

    • Client periodically writes heartbeat file to clients/{app}/
    • Server determines if Client is online via heartbeat file
    • Clients without heartbeat updates beyond timeout are marked offline

Configuration File Format

All configurations stored in JSON format for easy version control and manual review.

Base Configuration Example:

{
  "id": 1,
  "app": 1,
  "name": "User Level Activity",
  "scenes": "user_login,user_upgrade",
  "confId": 1,
  "status": 1,
  "debug": 0,
  "createAt": 1701234567890,
  "updateAt": 1701234567890
}

Conf Configuration Example:

{
  "id": 1,
  "app": 1,
  "confId": 1,
  "type": 5,
  "confName": "com.ice.test.flow.LevelFlow",
  "confField": "{\"level\": 5}",
  "sonIds": "2,3",
  "status": 1,
  "createAt": 1701234567890,
  "updateAt": 1701234567890
}

Multi-Instance Deployment

Shared Storage Solution

Multiple Server/Client instances need to share the same storage directory:

# docker-compose.yml example
services:
  ice-server-1:
    image: waitmoon/ice-server:2.0.0
    ports:
      - "8121:8121"
    volumes:
      - /shared/ice-data:/app/ice-data  # Shared storage

  ice-server-2:
    image: waitmoon/ice-server:2.0.0
    ports:
      - "8122:8121"
    volumes:
      - /shared/ice-data:/app/ice-data  # Same shared storage
  • NFS: Network File System, suitable for internal network environments
  • Cloud Drives: Alibaba Cloud NAS, AWS EFS, and other cloud storage services
  • Distributed File Systems: GlusterFS, CephFS, etc.

Combining with Expression Engine

Ice can integrate various expression engines like Aviator to simplify node configuration.

Example: Using Aviator as an example, if you want to create an Aviator-based Flow node:

@Data
@EqualsAndHashCode(callSuper = true)
public class AviatorFlow extends BaseLeafRoamFlow {

    private String exp; // Aviator expression for configuration and hot update

    private Expression compiledExpression;

    @Override
    protected boolean doRoamFlow(IceRoam roam) {
        return (boolean) compiledExpression.execute(roam);
    }

    public void setExp(String exp) { // For better performance, recompile when setting/updating expression
        this.exp = exp;
        this.compiledExpression = AviatorEvaluator.compile(exp, true);
    }
}

Node Error Handling

  • Unified Handling: IceErrorHandle

Set the handle instance of IceErrorHandle (inherit IceErrorHandle and implement the handle method), IceErrorHandle.setHandle(IceErrorHandle customHandle) to change unified error handling for all nodes; the default implementation is DefaultIceErrorHandle: directly return NodeRunStateEnum.SHUT_DOWN, do not process and terminate the entire flow.

  • Leaf Node Override errorHandle Method

Leaf node rewrites NodeRunStateEnum errorHandle(IceContext cxt, Throwable t) method to handle errors in current leaf node. If NodeRunStateEnum.SHUT_DOWN is returned, the entire flow will be terminated. If leaf node overrides errorHandle method, unified error handling won't be called, but can call super.errorHandle(cxt, t) for unified processing.

  • Configuration Handling

Node provides iceErrorStateEnum configuration. If not empty, it has highest priority and will use configuration as return value first. Configuration handling only affects return value, the errorHandle method/unified handle processing method will still be executed.

Server Configuration Details

Complete Ice Server configuration options:

server:
  port: 8121

ice:
  storage:
    # File storage path
    path: ./ice-data
  
  # Client inactive timeout (seconds)
  # Clients without heartbeat updates beyond this time will be marked offline
  client-timeout: 60
  
  # Version file retention count
  # Old version incremental files exceeding this count will be cleaned up
  version-retention: 1000

Client Configuration Details

Complete Ice Client configuration options:

ice:
  # Application ID (required)
  app: 1
  
  storage:
    # File storage path (required, needs to be shared with Server)
    path: ./ice-data
  
  # Leaf node scan package paths
  # Multiple packages separated by comma, not configured scans all (slower)
  scan: com.ice.test
  
  # Version polling interval (seconds), default 5 seconds
  poll-interval: 5
  
  # Heartbeat update interval (seconds), default 10 seconds
  heartbeat-interval: 10
  
  pool:
    # Thread pool parallelism, default -1 uses ForkJoinPool default configuration
    parallelism: -1