Installation
Basic usage
Loader
ZenEngine accepts an optional ZenLoader that serves decisions by key. Use Static, Filesystem, or Zip for common backends, or Callback for custom loading logic.
Static
Register decisions in memory. Use this when your rules ship with the application or are already loaded:File system
Load decisions from files under a root directory. Keys resolve to paths relative to the root:Zip archive
Pass the bytes of a zip archive. Every.json entry becomes a decision keyed by its path within the archive. This pairs naturally with BRMS release ZIPs - download the release from object storage and hand the bytes to the engine:
Custom loader
For any other backend, implementZenDecisionLoaderCallback and wrap it in ZenLoader.Callback:
null from the callback reports the key as not found.
Batch evaluation
Evaluate many requests in one call. Each result reports its own success or failure, so one bad input never fails the batch:Coroutines
Evaluation functions aresuspend functions, integrating natively with Kotlin coroutines:
Error handling
Tracing
Enable tracing to inspect decision execution:Expression utilities
Evaluate ZEN expressions outside of a decision context:Performance note
The Kotlin bindings use JNA (Java Native Access) for interoperability with the native Rust engine. This introduces some overhead compared to native Rust or direct bindings. We plan to revisit this when the FFM (Foreign Function & Memory) API becomes more widely adopted.
Best practices
Use.use {} for resource management. ZenEngine implements AutoCloseable to release native resources.
ZenEngine instance at application startup and reuse it for all evaluations.
Prefer declarative loaders. ZenLoader.Static, ZenLoader.Filesystem, and ZenLoader.Zip serve decisions from native code without callback overhead. Reserve ZenLoader.Callback for backends the built-in variants don’t cover.
Leverage coroutines for parallel evaluation. Use async/awaitAll to evaluate multiple decisions concurrently.