Skip to main content
Install the ZEN Engine and evaluate your first decision in Go.

Installation

go get github.com/gorules/zen-go

Basic usage

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. Use sync.Map to cache decisions for optimal performance.

File system

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

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

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

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

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:
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:
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 single ZenEngine 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.
engine := zen.NewEngine(zen.EngineConfig{Loader: loader})
defer engine.Dispose()
Use goroutines for parallel evaluation. Decision evaluation is thread-safe and works well with concurrent workloads.
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()