Ice organizes business rules as trees instead of chains
Rule engine/process engine, committed to solving flexible and complex hard-coded problems, for complex/flexibly changing business, provide a new abstract orchestration solution that is lightweight, high-performance and provides visual operation pages. Java规则引擎-ice,针对复杂/灵活变动业务,提供一个新的抽象编排解决方案,轻量级,高性能并提供可视化操作页面
At a glance
- What is it?
- Ice is a rule engine whose defining choice is that rules are trees, not chains or tables, so each node owns its own logic and changing one never cascades into the others. Nodes talk through a shared thread-safe data context called Roam rather than referencing each other, and the whole thing runs in memory with no database, message queue or service registry.
- Who is it for?
- Adopt Ice if you have business logic that changes often and is currently hard-coded in branching code, since the tree model makes rules configurable in a web editor and hot-reloadable in seconds. Skip it if you need transactional guarantees or durable shared state, because the design deliberately has no database and the server and client communicate through a shared directory.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 167 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Trees, not chains, and why that changes everything
Ice is a rule engine that takes a fundamentally different approach, and the first line of the explanation is the whole design in one sentence. Rules are organized as trees, not chains or tables, each node in the tree handles its own logic independently, and modifying one node never creates cascading effects on others.
That last clause is the point, and it is a real difference from the usual shape of a rules engine. A chain of rules, whether a decision table or a linear list of conditions, has an implicit coupling: changing rule three changes what happens when rules one and two pass, because the downstream rules see a context that upstream rules have already modified.
The tree layout removes that by giving each node its own inputs and outputs and preventing nodes from talking to each other directly. The claimed result is a system where business rules can be visually configured, hot-reloaded in seconds, and executed in-memory with sub-millisecond latency, all without requiring any database or middleware.
The tagline calls Ice a lightweight, zero-dependency rule engine with visual tree-based orchestration, and the zero dependency claim is spelled out in the feature table as no database, no message queue, no service registry, just files on disk.
That combination of tree structure and no infrastructure is what makes the project interesting. Most rule engines are sold as a way to move logic out of code, and they bring a database, a deployment model and a consistency story with them. This one brings files.
The project is Go, Apache-2.0 licensed, with the homepage at waitmoon.com and a Chinese README alongside the English one. The last push was on 2026-05-09 and the recent tags are v4.0.12, v4.0.11 and v4.0.10, all from 2026.
Two kinds of node, and the five relation types
Every rule in Ice is a tree composed of two types of nodes. Relation nodes control the execution flow, similar to logical operators, and leaf nodes contain your actual business logic.
The relation node table is where the semantics are defined, and the short-circuit column is the detail worth reading. AND means all children must return true, and it short-circuits. ANY means at least one child returns true, and it short-circuits. ALL means all children must return true, with no short-circuit. NONE means no child returns true, also without short-circuit. And TRUE always returns true, executing all children for side effects, again without short-circuit.
The distinction between AND and ALL is the interesting one. Both require every child to return true, but AND stops at the first false child while ALL evaluates all of them. That is a meaningful difference when a child has side effects, or when you want to collect information from every branch rather than only from the successful ones.
Each type has a parallel variant, P_AND, P_ANY, P_ALL, P_NONE and P_TRUE, which executes children concurrently. The parallel relation nodes are a listed feature of the project in their own right, under parallel execution, so concurrent child execution is a first-class part of the model rather than an optimisation bolted on.
The leaf node types are simpler. Flow returns a boolean and is for conditional checks, the kind of question that is phrased as is the user eligible. Result returns a boolean and is for business operations, like issue the coupon. And None returns void and is for side effects, such as queries, logging, metrics and notifications.
So the tree reads as a sentence. A relation node at the top says how the branches combine, and each branch is either a question, an action or a side effect.
Roam is the only channel between nodes
Roam is the mechanism that makes node isolation possible, and the description of it is short enough to quote in full. It is the thread-safe data container that flows through the entire rule tree, nodes read input from and write output to Roam, and they never reference each other directly. This is the key to node isolation.
The operations look like this:
Roam
├── put("uid", 12345) // flat key-value
├── putDeep("user.level", "gold") // nested key
├── get("uid") // → 12345
└── getDeep("user.level") // → "gold"The flat and deep variants are the difference between a map of scalars and a tree of structures, and the deep key auto-creates the nested structures as it writes, so a node does not have to know whether user.level already exists.
The thread safety is implemented differently per language, which tells you the SDKs are genuine ports rather than one implementation with wrappers. Java uses a ConcurrentHashMap, Go uses a sync.RWMutex, and Python uses a threading.RLock.
The dynamic references feature is the fourth piece and the most useful for a rule author. You prefix a value with @ to resolve it from Roam at runtime, so a value written as @uid reads roam.get("uid") at the moment it is used rather than when it was configured.
That distinction matters for the hot reload story. If a value were captured at configuration time, a rule that referenced it would keep using the old value after a reload. Resolving at runtime means the current Roam contents are used, which is what lets a reloaded rule take effect immediately against live data.
The server and the client share a directory and nothing else
The architecture diagram is the part of the documentation that explains why the zero-dependency claim is plausible rather than merely aspirational.
There is a shared storage directory, ice-data/, with four subdirectories: apps/, bases/, confs/ and versions/. The Ice Server writes to it, with the arrows labelled Write and Apply & publish, and the Ice Client reads from it, with the arrow labelled Read and Poll.
The server side carries the Web UI, the tree editor, the Apply & publish action and mock debugging. The client side carries version polling, hot reload, in-memory execution, mock execution and fault tolerance.
Three design decisions are called out, and each one explains a behaviour you would otherwise find surprising.
Server and client are fully decoupled, so if the Server goes down the Clients continue executing rules from their in-memory cache. That is a strong availability property, and it comes from the fact that the client needs nothing from the server to execute a rule it already has.
File-based communication means there is no network protocol between Server and Client, just a shared directory. So the two are not talking over HTTP, and the deployment model is about where that directory is mounted.
Incremental updates means the Client polls versions/ for changes and applies only the delta, falling back to a full reload if the incremental files are missing. The fallback is the detail that makes the incremental path safe to rely on.
The three deployment options follow directly from that. Single Machine has the server and client on the same host with ice-data/ as a local directory. Docker Compose is the containerized deployment with volume mounts. And Distributed has multiple servers and clients sharing storage via NFS, AWS EFS, or another shared filesystem.
Hot reload, node reuse and lane isolation
Four of the ten features in the table are about how you change and reuse rules rather than about what they do, and they are the ones that affect day-to-day work.
Hot reload is the headline. Changes take effect in seconds via automatic version polling, with no restart needed, which is the client side of the architecture. Combined with the visual web editor, which lets you configure rules through a tree editor with no DSL to learn, the feedback loop is close to what you would expect from a form rather than from code.
Node reuse is the one that pays off over time. The same node can be shared across multiple rule trees, so a piece of logic that is used in five places is defined once. That is the argument for trees over tables, since in a table the shared condition tends to get duplicated.
Mock debugging is the feature that makes the tree structure observable. You can trigger rule execution remotely from the web UI for real-time debugging, and the client side lists mock execution as one of its capabilities. In a system where the rules are configuration rather than code, being able to fire a rule against real data and see which branches ran is the thing that makes the configuration safe to change.
Lane and traffic isolation is the one with the widest scope. It is branch-based rule isolation for A/B testing, canary releases and gradual rollouts, so the same rule tree can have different branches for different traffic, which is how you would release a change to a slice of your users and then widen it. That is a release engineering capability inside a rule engine, and it is unusual to see it offered as a built-in feature rather than something you build on top.
The remaining features are the ones already covered, parallel execution for concurrent child execution, and the high performance claim of pure in-memory execution with sub-millisecond latency and zero network overhead, which follows from the client running rules locally.
Multi-language SDKs and how the project is packaged
Ice ships SDKs for Java, Go and Python, with the feature table claiming full feature parity across all three, and the packaging reflects that.
There is a Maven Central badge for the artifact com.waitmoon.ice.ice, a Go reference badge for the sdks/go path, and a PyPI badge for a package named ice-rules, which is an unusually friendly naming rather than a uniform one. The Docker badge points at waitmoon/ice-server on Docker Hub.
The repository layout backs the multi-language claim. There is a server/ directory, an sdks/ directory holding the client libraries for each language, a tests/ directory, and the usual project metadata files.
The version history gives a flavour of what the maintainers have been working on, since the release tags carry descriptions. v4.0.12 is 版本递增 & 新建节点属性回显修复, a version increment and a fix for echoing back properties of newly created nodes. v4.0.11 is a dark mode fix and a label improvement. And v4.0.10 is Change Comparison & Dual-Tree Diff.
That last one is worth pausing on, because it tells you what the editor was missing. Comparing two rule versions side by side, with a dual-tree diff, is the feature you need before you can safely change a rule that other people depend on. A tree editor without a diff view makes every change risky, because you cannot see what you are about to alter.
The dark mode fix and the label improvement suggest attention to the everyday usability of the web UI, which is the surface the whole product is judged on. The documentation is at waitmoon.com with an English and a Chinese version, there is a live demo at eg.waitmoon.com, and a getting started guide linked from the top.
So the picture is of a product-shaped project rather than a library: a server with a web editor, a client library in three languages, a container image, a live demo and a documentation site.
Editorial conclusion
Adopt Ice if you have business logic that changes often and is currently hard-coded in branching code, since the tree model makes rules configurable in a web editor and hot-reloadable in seconds. Skip it if you need transactional guarantees or durable shared state, because the design deliberately has no database and the server and client communicate through a shared directory. Verify first which deployment mode you want, since single machine, Docker Compose and distributed all change where the shared directory lives, that your rules genuinely fit the tree model with relation nodes above leaf nodes, and how the lane feature is used, since branch isolation for canary releases is a feature with policy implications.
Frequently asked questions
What is Ice and how are its rules structured?
Ice is a rule engine that organizes rules as trees rather than chains or tables. Every rule is composed of relation nodes, which control execution flow like logical operators, and leaf nodes, which hold the business logic. Relation nodes come in AND, ANY, ALL, NONE and TRUE variants, each with a parallel variant that executes children concurrently.
What is Roam and why do the nodes not reference each other?
Roam is the thread-safe data container that flows through the entire rule tree. Nodes read input from and write output to Roam and never reference each other directly, which is what makes node isolation work and stops a change to one node from cascading into others. It supports flat and deep keys, and a value prefixed with @ is resolved from Roam at runtime.
How do the Ice server and client communicate, and what happens if the server goes down?
They communicate through a shared directory called ice-data/ with apps, bases, confs and versions subdirectories, and there is no network protocol between them. The client polls versions/ for changes and applies only the delta, falling back to a full reload if the incremental files are missing. Because the two are fully decoupled, clients keep executing rules from their in-memory cache if the server goes down.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zjn-zjn-ice)Was this useful?