IceIce
Home
  • Getting Started

    • Quick Start
    • Core Concepts
    • Architecture
  • SDK Guide

    • Java SDK
    • Go SDK
    • Python SDK
  • Reference

    • Node Types
    • Roam API
    • Server Config
    • Client Config
Playground
FAQ
  • Changelog
  • Upgrade Guide
Sponsor
Community
GitHub
  • English
  • 简体中文
Home
  • Getting Started

    • Quick Start
    • Core Concepts
    • Architecture
  • SDK Guide

    • Java SDK
    • Go SDK
    • Python SDK
  • Reference

    • Node Types
    • Roam API
    • Server Config
    • Client Config
Playground
FAQ
  • Changelog
  • Upgrade Guide
Sponsor
Community
GitHub
  • English
  • 简体中文
  • Guide

    • Getting Started
    • Core Concepts
    • Architecture
    • FAQ

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

TypeExecutionReturn LogicAnalogy
ANDSequential, stops on falseAll true returns true; any false returns falseJava &&
ANYSequential, stops on trueAny true returns true; all false returns falseJava ||
ALLExecutes all, no short-circuitHas true and no false returns true; any false returns false
NONEExecutes allAlways returns none
TRUEExecutes allAlways 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:

TypeReturn ValuePurposeExamples
Flowtrue / falseConditional checks that control flow directionAmount validation, level checks, time filtering
Resulttrue / falseExecute business operations and return resultsIssue coupons, deduct inventory, send notifications
NonenoneAuxiliary operations that don't affect flowQuery 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 LevelInputUse Case
BaseLeaf*IceContextNeed access to full context (iceId, processInfo, etc.)
BaseLeafPack*IcePackNeed access to requestTime, traceId, and other pack info
BaseLeafRoam*IceRoamOnly 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:

FieldTypeDescription
iceIdlongID of the rule to trigger
sceneStringScene name to trigger; all rules subscribed to this scene will execute
confIdlongExecute with the specified node as root
roamIceRoamBusiness data container
requestTimelongRequest timestamp (affects node time window evaluation)
traceIdStringTrace ID for distributed tracing, auto-generated
debugbyteDebug 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:

ConfigurationDescription
Time WindowSets the effective time range for a node. Nodes outside the time window are treated as non-existent
InverseInverts the node's true result to false and vice versa (NONE is not affected)
Error HandlingBehavior when a node throws an error: terminate the flow (default) or return a specified state to continue
Node ReuseThe 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

Edit this page on GitHub
Prev
Getting Started
Next
Architecture