> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gorules.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Go Rules Engine

> Integrate GoRules into your Go application.

Install the ZEN Engine and evaluate your first decision in Go.

## Installation

```bash theme={null}
go get github.com/gorules/zen-go/v2
```

<Note>
  **Upgrading from v0.x?** Version 2 changes the import path to `github.com/gorules/zen-go/v2`, and callback loaders now require the `zen.Loader` wrapper: `EngineConfig{Loader: zen.Loader(myFunc)}`. Loader configurations (`StaticLoader`, `FilesystemLoader`, `ZipLoader`) and batch evaluation are new in v2.
</Note>

## Basic usage

```go theme={null}
package main

import (
    "fmt"
    "os"

    zen "github.com/gorules/zen-go/v2"
)

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}
}
```

## Loaders

Loaders let the engine resolve decisions by key - `engine.Evaluate("pricing.json", ...)` - instead of you reading files by hand. Set `EngineConfig.Loader` to a built-in loader config (`StaticLoader`, `FilesystemLoader`, or `ZipLoader`) or a custom callback.

### Static

The static loader serves decisions from an in-memory map. Use it when your rules ship with the application or arrive as one payload:

```go theme={null}
package main

import (
    "encoding/json"
    "fmt"
    "os"

    zen "github.com/gorules/zen-go/v2"
)

func main() {
    pricing, _ := os.ReadFile("./pricing-rules.json")

    engine := zen.NewEngine(zen.EngineConfig{
        Loader: zen.StaticLoader{
            Content: map[string]json.RawMessage{
                "pricing.json": pricing,
            },
        },
    })
    defer engine.Dispose()

    response, _ := engine.Evaluate("pricing.json", map[string]any{"amount": 100})
    fmt.Println(string(response.Result))
}
```

### File system

The filesystem loader resolves keys against a root directory - `pricing.json` maps to `./rules/pricing.json`:

```go theme={null}
engine := zen.NewEngine(zen.EngineConfig{
    Loader: zen.FilesystemLoader{Path: "./rules"},
})
defer engine.Dispose()

response, _ := engine.Evaluate("pricing.json", map[string]any{"amount": 100})
```

### Zip archive

The zip loader unpacks an archive in memory; every `.json` entry becomes a decision keyed by its path in the archive. This pairs naturally with [release ZIPs](/brms/deploy/releases) downloaded from the BRMS or object storage:

```go theme={null}
package main

import (
    "context"
    "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/v2"
)

func main() {
    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("release-1.0.0.zip"),
    })
    defer result.Body.Close()
    zipBytes, _ := io.ReadAll(result.Body)

    engine := zen.NewEngine(zen.EngineConfig{
        Loader: zen.ZipLoader{Bytes: zipBytes},
    })
    defer engine.Dispose()

    response, _ := engine.Evaluate("pricing.json", map[string]any{"amount": 100})
    fmt.Println(string(response.Result))
}
```

### Custom loader

For any other backend - a database, a remote API, per-tenant storage - pass a callback. Use `sync.Map` to cache at the source:

```go theme={null}
package main

import (
    "fmt"
    "os"
    "path/filepath"
    "sync"

    zen "github.com/gorules/zen-go/v2"
)

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: zen.Loader(loader)})
    defer engine.Dispose()

    response, _ := engine.Evaluate("pricing.json", map[string]any{"amount": 100})
    fmt.Println(string(response.Result))
}
```

## Batch evaluation

Evaluate many requests in one call. Each result reports its own success or failure, so one bad input never fails the batch:

```go theme={null}
results, err := engine.EvaluateBatch([]zen.EvaluateBatchRequest{
    {Key: "pricing.json", Context: map[string]any{"amount": 100}},
    {Key: "pricing.json", Context: map[string]any{"amount": 250}},
    {Key: "eligibility.json", Context: map[string]any{"age": 30}},
})
if err != nil {
    log.Fatal(err)
}

for _, result := range results {
    if result.Success {
        fmt.Println(string(result.Data.Result))
    } else {
        fmt.Println("Evaluation failed:", string(result.Error))
    }
}
```

## Error handling

```go theme={null}
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:

```go theme={null}
response, err := decision.EvaluateWithOpts(input, zen.EvaluationOptions{
    Trace: true,
})
if err != nil {
    return err
}

if response.Trace != nil {
    fmt.Println(string(*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:

```go theme={null}
import zen "github.com/gorules/zen-go/v2"

// 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.

**Prefer built-in loaders.** `StaticLoader`, `FilesystemLoader`, and `ZipLoader` cover most setups without custom code; reserve callback loaders for backends the built-ins can't reach.

**Call `Dispose()` on cleanup.** Release engine and decision resources when your application terminates.

```go theme={null}
engine := zen.NewEngine(zen.EngineConfig{Loader: zen.Loader(loader)})
defer engine.Dispose()
```

**Use goroutines for parallel evaluation.** Decision evaluation is thread-safe and works well with concurrent workloads.

```go theme={null}
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()
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.