JudgeJson

An Elixir rule engine where rules are json objects. The judge gives verdicts on data and returns any matched rules.

What and why are rule engines useful?

A rules engine is a flexible piece of software that manages business rules.

Rule = Condition + Action

Think of business rules as dynamically loaded “if-then” statements. A rules engine fits really well with event hooks, event handling, and ETL flows. Generic webhook endpoint → rule match some condition → route / handle payload.

A general design pattern:

The main benefits:

If you store rules in a DB, you can change your business logic on the fly during runtime. Essentially hot swap your business logic without reloading an app or changing code. It also makes changes much more maintainable with faster turn around time.

Another understated benefit; you can isolate team and company concerns. One team / group can manage business rule CRUD lifecycle while another specialized dev team manages execution logic (action flows and handlers etc). None-coders can create new rules through a form gui and quickly change automations.

Installation

Available in Hex, the package can be installed by adding judge_json to your list of dependencies in mix.exs:

def deps do
  [
    {:judge_json, ">= 1.0.0"}
  ]
end

Quick Start Example

Run in Livebook

iex> payload = to_string('
{
    "data": {
        "person": {
            "name": "Lionel",
            "last_name": "Messi",
            "interests": [
                "soccer",
                "hot dogs",
                "sports"
            ]
        }
    },
    "rules": [
        {
            "id": "123456",
            "conditions": {
                "all": [
                    {
                        "path": "/person/name",
                        "operator": "equals",
                        "value": "Lionel"
                    },
                    {
                        "path": "/person/last_name",
                        "operator": "like",
                        "value": "mess"
                    },
                    {
                        "path": "/person/interests",
                        "operator": "contains",
                        "value": "soccer"
                    }
                ]
            },
            "action": "collect_signature.exs"
        }
    ]
}')
iex>
iex> results = JudgeJson.evaluate(payload)
iex>
iex> [
  %{
    "action" => "collect_signature.exs",
    "conditions" => %{
      "all" => [
        %{"operator" => "equals", "path" => "/person/name", "value" => "Lionel"},
        %{
          "operator" => "like",
          "path" => "/person/last_name",
          "value" => "mess"
        },
        %{
          "operator" => "contains",
          "path" => "/person/interests",
          "value" => "soccer"
        }
      ]
    },
    "id" => "123456"
  }
]

Notes:

Documention and Usage

Docs can be found at https://hexdocs.pm/judge_json

Judge Json is storage and action agnostic. Ideally you can store/load rules from a DB and evaluate on incoming json payloads. Handler code can then evaluate rules and action however you like.

Rule Schema

The rule schema is very flexible and the only strict requirements are on the conditions. You can add your own rule id and action schema. If a rule is matched, the entire rule object is appended to the result list.

Example:

{
    "id": "",
    "conditions": {},
    "action": {}
}

Example:

{
    "id": "d6f05047-807d-4970-b411-5575fb739dda",
    "conditions": {},
    "action": "on_match_script.exs",
    "meta_data": {}
}

Conditions

Conditions consist of a path, operator, and value. Example:

{
    "path": "/person/name",
    "operator": "contains",
    "value": "ABC"
}

Path

A rfc json-pointer for the supplied json payload.

Operators

Value

The value to match or operate on

Condition sets

Features:

Example:

{
    "any": [
        {
            "path": "/source",
            "operator": "contains",
            "value": "X"
        },
        {
            "path": "/product",
            "operator": "contains",
            "value": "Y"
        }
    ]
}

Example:

{
    "all": [
        {
            "path": "/name",
            "operator": "equals",
            "value": "A"
        },
        {
            "any": [
                {
                    "path": "/source",
                    "operator": "contains",
                    "value": "X"
                },
                {
                    "path": "/product",
                    "operator": "contains",
                    "value": "Y"
                }
            ]
        }
    ]
}

Credits

This project is inspired by: