Installation
Copy
go get github.com/gorules/zen-go
Basic usage
Copy
package main
import (
"fmt"
"os"
zen "github.com/gorules/zen-go"
)
func main() {
content, _ := os.ReadFile("./pricing-rules.json")
engine := zen.NewEngine(zen.EngineConfig{})
defer engine.Dispose()
decision, _ := engine.CreateDecision(content)
defer decision.Dispose()
response, _ := decision.Evaluate(map[string]any{
"customer": map[string]any{"tier": "gold", "yearsActive": 3},
"order": map[string]any{"subtotal": 150, "items": 5},
})
fmt.Println(string(response.Result))
// => {"discount":0.15,"freeShipping":true}
}
Loader
The loader pattern enables dynamic decision loading from any storage backend. Usesync.Map to cache decisions for optimal performance.
File system
Copy
package main
import (
"fmt"
"os"
"path/filepath"
"sync"
zen "github.com/gorules/zen-go"
)
var cache sync.Map
func loader(key string) ([]byte, error) {
if data, ok := cache.Load(key); ok {
return data.([]byte), nil
}
content, err := os.ReadFile(filepath.Join("./rules", key))
if err != nil {
return nil, err
}
cache.Store(key, content)
return content, nil
}
func main() {
engine := zen.NewEngine(zen.EngineConfig{Loader: loader})
defer engine.Dispose()
response, _ := engine.Evaluate("pricing.json", map[string]any{"amount": 100})
fmt.Println(string(response.Result))
}
AWS S3
Copy
package main
import (
"archive/zip"
"bytes"
"context"
"errors"
"fmt"
"io"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
zen "github.com/gorules/zen-go"
)
var rules = make(map[string][]byte)
func main() {
// Download and extract all decisions at startup
cfg, _ := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-east-1"))
s3Client := s3.NewFromConfig(cfg)
result, _ := s3Client.GetObject(context.TODO(), &s3.GetObjectInput{
Bucket: aws.String("my-rules-bucket"),
Key: aws.String("decisions.zip"),
})
defer result.Body.Close()
zipBytes, _ := io.ReadAll(result.Body)
zipReader, _ := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
for _, file := range zipReader.File {
if !file.FileInfo().IsDir() {
rc, _ := file.Open()
content, _ := io.ReadAll(rc)
rc.Close()
rules[file.Name] = content
}
}
engine := zen.NewEngine(zen.EngineConfig{
Loader: func(key string) ([]byte, error) {
if data, ok := rules[key]; ok {
return data, nil
}
return nil, errors.New("decision not found: " + key)
},
})
defer engine.Dispose()
response, _ := engine.Evaluate("pricing.json", map[string]any{"amount": 100})
fmt.Println(string(response.Result))
}
Azure Blob Storage
Copy
package main
import (
"archive/zip"
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
zen "github.com/gorules/zen-go"
)
var rules = make(map[string][]byte)
func main() {
// Download and extract all decisions at startup
client, _ := azblob.NewClientFromConnectionString(os.Getenv("AZURE_STORAGE_CONNECTION"), nil)
containerClient := client.ServiceClient().NewContainerClient("rules")
blobClient := containerClient.NewBlobClient("decisions.zip")
resp, _ := blobClient.DownloadStream(context.TODO(), nil)
defer resp.Body.Close()
zipBytes, _ := io.ReadAll(resp.Body)
zipReader, _ := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
for _, file := range zipReader.File {
if !file.FileInfo().IsDir() {
rc, _ := file.Open()
content, _ := io.ReadAll(rc)
rc.Close()
rules[file.Name] = content
}
}
engine := zen.NewEngine(zen.EngineConfig{
Loader: func(key string) ([]byte, error) {
if data, ok := rules[key]; ok {
return data, nil
}
return nil, errors.New("decision not found: " + key)
},
})
defer engine.Dispose()
response, _ := engine.Evaluate("pricing.json", map[string]any{"amount": 100})
fmt.Println(string(response.Result))
}
Google Cloud Storage
Copy
package main
import (
"archive/zip"
"bytes"
"context"
"errors"
"fmt"
"io"
"cloud.google.com/go/storage"
zen "github.com/gorules/zen-go"
)
var rules = make(map[string][]byte)
func main() {
// Download and extract all decisions at startup
client, _ := storage.NewClient(context.TODO())
bucket := client.Bucket("my-rules-bucket")
reader, _ := bucket.Object("decisions.zip").NewReader(context.TODO())
defer reader.Close()
zipBytes, _ := io.ReadAll(reader)
zipReader, _ := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
for _, file := range zipReader.File {
if !file.FileInfo().IsDir() {
rc, _ := file.Open()
content, _ := io.ReadAll(rc)
rc.Close()
rules[file.Name] = content
}
}
engine := zen.NewEngine(zen.EngineConfig{
Loader: func(key string) ([]byte, error) {
if data, ok := rules[key]; ok {
return data, nil
}
return nil, errors.New("decision not found: " + key)
},
})
defer engine.Dispose()
response, _ := engine.Evaluate("pricing.json", map[string]any{"amount": 100})
fmt.Println(string(response.Result))
}
Error handling
Copy
response, err := decision.Evaluate(input)
if err != nil {
log.Printf("Evaluation failed: %v", err)
return err
}
fmt.Println(string(response.Result))
Tracing
Enable tracing to inspect decision execution:Copy
response, err := decision.EvaluateWithOpts(input, zen.EvaluationOptions{
Trace: true,
})
if err != nil {
return err
}
fmt.Println(response.Trace)
// Each node's input, output, and performance timing
fmt.Println(response.Performance)
// Total evaluation time
Expression utilities
Evaluate ZEN expressions outside of a decision context:Copy
import zen "github.com/gorules/zen-go"
// Standard expressions (with generics)
result, _ := zen.EvaluateExpression[int]("a + b", map[string]any{"a": 5, "b": 3})
// => 8
total, _ := zen.EvaluateExpression[int]("sum(items)", map[string]any{"items": []int{1, 2, 3, 4}})
// => 10
// Unary expressions (comparison against $)
isValid, _ := zen.EvaluateUnaryExpression(">= 5", map[string]any{"$": 10})
// => true
inList, _ := zen.EvaluateUnaryExpression("'US', 'CA', 'MX'", map[string]any{"$": "US"})
// => true
Best practices
Initialize the engine once. Create a singleZenEngine instance at application startup and reuse it for all evaluations.
Implement a loader for dynamic decisions. The loader pattern centralizes decision loading logic and enables caching at the source.
Call Dispose() on cleanup. Release engine and decision resources when your application terminates.
Copy
engine := zen.NewEngine(zen.EngineConfig{Loader: loader})
defer engine.Dispose()
Copy
var wg sync.WaitGroup
for _, input := range inputs {
wg.Add(1)
go func(in map[string]any) {
defer wg.Done()
response, _ := engine.Evaluate("pricing.json", in)
// process response
}(input)
}
wg.Wait()