Crates.io | zen-engine |
lib.rs | zen-engine |
version | 0.33.0 |
source | src |
created_at | 2023-03-13 17:19:27.211029 |
updated_at | 2024-10-23 14:10:00.709002 |
description | Business rules engine |
homepage | |
repository | https://github.com/gorules/zen.git |
max_upload_size | |
id | 809082 |
size | 260,372 |
ZEN Engine is business friendly Open-Source Business Rules Engine (BRE) to execute decision models according to the GoRules JSON Decision Model (JDM) standard. It is written in Rust and provides native bindings for NodeJS and Python. ZEN Engine allows to load and execute JSON Decision Model (JDM) from JSON files.
Add the following to your Cargo.toml file:
[dependencies]
zen-engine = "0"
To execute a simple decision using a Noop (default) loader you can use the code below.
use serde_json::json;
use zen_engine::DecisionEngine;
use zen_engine::model::DecisionContent;
async fn evaluate() {
let decision_content: DecisionContent = serde_json::from_str(include_str!("jdm_graph.json")).unwrap();
let engine = DecisionEngine::default();
let decision = engine.create_decision(decision_content.into());
let result = decision.evaluate(&json!({ "input": 12 })).await;
}
Alternatively, you may create decision indirectly without constructing the engine utilising
Decision::from
function.
For more advanced use cases where you want to load multiple decisions and utilise graphs you may use one of the following pre-made loaders:
Arc<DecisionContent>
instanceAssuming that you have a folder with decision models (.json files) which is located under /app/decisions, you may use FilesystemLoader in the following way:
use serde_json::json;
use zen_engine::DecisionEngine;
use zen_engine::loader::{FilesystemLoader, FilesystemLoaderOptions};
async fn evaluate() {
let engine = DecisionEngine::new(FilesystemLoader::new(FilesystemLoaderOptions {
keep_in_memory: true, // optionally, keep in memory for increase performance
root: "/app/decisions"
}));
let context = json!({ "customer": { "joinedAt": "2022-01-01" } });
// If you plan on using it multiple times, you may cache JDM for minor performance gains
// In case of bindings (in other languages, this increase is much greater)
{
let promotion_decision = engine.get_decision("commercial/promotion.json").await.unwrap();
let result = promotion_decision.evaluate(&context).await.unwrap();
}
// Or on demand
{
let result = engine.evaluate("commercial/promotion.json", &context).await.unwrap();
}
}
You may create a custom loader for zen engine by implementing DecisionLoader
trait.
Here's an example of how MemoryLoader has been implemented.
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use zen_engine::loader::{DecisionLoader, LoaderError, LoaderResponse};
use zen_engine::model::DecisionContent;
#[derive(Debug, Default)]
pub struct MemoryLoader {
memory_refs: RwLock<HashMap<String, Arc<DecisionContent>>>,
}
impl MemoryLoader {
pub fn add<K, D>(&self, key: K, content: D)
where
K: Into<String>,
D: Into<DecisionContent>,
{
let mut mref = self.memory_refs.write().unwrap();
mref.insert(key.into(), Arc::new(content.into()));
}
pub fn get<K>(&self, key: K) -> Option<Arc<DecisionContent>>
where
K: AsRef<str>,
{
let mref = self.memory_refs.read().unwrap();
mref.get(key.as_ref()).map(|r| r.clone())
}
pub fn remove<K>(&self, key: K) -> bool
where
K: AsRef<str>,
{
let mut mref = self.memory_refs.write().unwrap();
mref.remove(key.as_ref()).is_some()
}
}
impl DecisionLoader for MemoryLoader {
fn load<'a>(&'a self, key: &'a str) -> impl Future<Output=LoaderResponse> + 'a {
async move {
self.get(&key)
.ok_or_else(|| LoaderError::NotFound(key.to_string()).into())
}
}
}