Rulio
Go Shell JavaScript Makefile
Switch branches/tags
Nothing to show
Latest commit ec19e1f Feb 22, 2018

README.md

Build Status

Rulio is a rules engine

Overview

A rules engine. You write rules and send events. You can also write some facts that rules can use. When an event arrives, the system finds candidate rules. A candidate rule's condition is evaluated to find zero or more sets of variable bindings. For each set of variable bindings, the rule's actions are executed.

See the docs for more. In particular, see doc/Manual.md. There are lots of examples in examples/.

Also see Comcast/sheens.

License

This software is released under the Apache License, Version 2.0. See LICENSE in this repo.

Usage

Starting

To compile, you need Go. Then

(cd rulesys && go get . && go install)
bin/startengine.sh &
ENDPOINT=http://localhost:8001
LOCATION=here
curl -s $ENDPOINT/version

If you see some JSON, the engine is probably running. Check engine.log to see some logging.

A simple rule

Now let's use that engine. In these examples, we'll talk to the engine using its primitive HTTP API.

# Write a fact.
curl -s -d 'fact={"have":"tacos"}' "$ENDPOINT/api/loc/facts/add?location=$LOCATION"

# Query for the fun of it.
curl -s -d 'pattern={"have":"?x"}' "$ENDPOINT/api/loc/facts/search?location=$LOCATION" | \
  python -mjson.tool

# Write a simple rule.
cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"when":{"pattern":{"wants":"?x"}},
          "condition":{"pattern":{"have":"?x"}},
          "action":{"code":"var msg = 'eat ' + x; console.log(msg); msg;"}}}
EOF

# Send an event.
curl -d 'event={"wants":"tacos"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool

The events/ingest output is pretty big. This data contains sufficient information to enable you to reattempt/resume event processing in the case the engine encountered one or more errors during the previous processing.

Scheduled rule

Now let's write a little scheduled rule.

# First a quick check to see if a Javascript action can give us a timestamp.
curl -d 'code=new Date().toISOString()' $ENDPOINT/api/sys/util/js

# Write a scheduled rule.
cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"schedule":"+3s",
          "condition":{"pattern":{"have":"?x"}},
          "action":{"code":"console.log('eating ' + x + ' at ' + (new Date().toISOString()) + '.');"}}}
EOF

Look for a line starting with eating tacos in the engine output.

grep -F 'eating tacos' engine.log

That rule runs only once. Three seconds from when it was created. (We can also use full cron syntax to specify a repeating schedule.)

Action talking to an external service

Now let's make a rule with an action that talks to an external service. We'll start a dummy service that just prints out what it hears.

# Start our dummy service.  Use another window.
(cd examples && ./endpoint.py) &

# See if it works.
curl "http://localhost:6668/foo?likes=tacos"
# Should see some data in that service's window.

# Write the rule.  This rule has no condition.
cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"when":{"pattern":{"wants":"?x"}},
          "action":{"code":"Env.http('GET','http://localhost:6668/do?order=' + Env.encode(x))"}}}
EOF

# Send an event.  Should trigger that action.
curl -d 'event={"wants":"Higher Math"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool
# You should see an order in the `endpoint.py' output.

Rule condition querying an external service

The rules engine can query external services during rule condition evaluation. Such a service is called an "external fact service". We have a few example fact services in examples/. Here's one that can report the weather.

(cd examples && ./weatherfs.py) &

# Test it.
curl -d '{"locale":"Austin,TX","temp":"?x"}' 'http://localhost:6666/facts/search'

# Write a rule that uses that source of facts.
cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"when":{"pattern":{"visitor":"?who"}, "location":"here"},
          "condition":{"and":[{"pattern":{"locale":"Austin,TX","temp":"?temp"},
                               "locations":["http://localhost:6666/facts/search"]},
                              {"code":"console.log('temp: ' + temp); 0 < temp"}]},
          "action":{"code":"Env.http('GET','http://localhost:6668/report?weather=' + Env.encode('warm enough'))"}}}
EOF

# Send an event.  Should trigger that action.
curl -d 'event={"visitor":"Homer"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool

Javascript libraries

Let's use a library of Javascript code in a rule action.

# Start a library server.
(cd examples && ./libraries.py) &

# Check that we can get a library.
curl http://localhost:6669/libs/tester.js

curl "$ENDPOINT/api/loc/admin/clear?location=$LOCATION"

# Write the rule.
cat <<EOF | curl -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule": {"when":{"pattern":{"wants":"?x"}},
          "condition":{"code":"isGood(x)",
                       "libraries":["http://localhost:6669/libs/tester.js"]},
          "action":{"code":"var msg = 'Serve ' + x + ' ('+ isGood(x) + ')'; console.log(msg); msg;",
                    "opts":{"libraries":["http://localhost:6669/libs/tester.js"]}}}}
EOF

# Send an event.  Should trigger that action.
curl -d 'event={"wants":"Higher Math"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool

# Send another event.  Should not trigger that action.
curl -d 'event={"wants":"Duff Light"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
   python -mjson.tool

You can also use libraries in Javascript rule actions.

Your libraries can be pretty fancy (see example/libs/haversine.js), but be cautious about efficiency and robustness. If you find yourself wanting to do a lot of work in action Javascript, think about writing an action executor instead.

Action executors

If you don't want to write your actions in Javascript, which runs inside the rules engine, you can use action executors. An action executor is an external service that is given rule actions to execute. In a serious deployment, an action executor endpoint would probably just queue those actions for a pool of workers to process.

An action executor can do or execute anything in any language or specification. Up to the author of the executor.

We have an example action executor in Python in examples/executor.py.

# Run the toy action executor.
(cd examples && ./executor.py) &

cat <<EOF | curl -s -d "@-" "$ENDPOINT/api/loc/rules/add?location=$LOCATION"
{"rule":{"when":{"pattern":{"drinks":"?x"}},
         "action":{"endpoint":"http://localhost:8081/execbash",
                   "code":{"order":"?x"}}}}
EOF

# Send an event.
curl -d 'event={"drinks":"milk"}' "$ENDPOINT/api/loc/events/ingest?location=$LOCATION" | \
  python -mjson.tool

That event should generate a request to the example action executor, which doesn't actually do anything.

You can also write your own action interpreters. For example, you could write an interpreter (shim) for Bash script actions and really cause some trouble.

Getting some statistics

Finally, let's get some statistics.

curl -s "$ENDPOINT/api/loc/admin/stats?location=$LOCATION" | python -mjson.tool
curl -s "$ENDPOINT/api/sys/stats" | python -mjson.tool

APIs

The engine is designed in packages. The core is in core/, and sys/ provides something like a container for locations.

The network API used above and provided by service/ is rendered in HTTP, but it was originally designed for message-oriented transports. For example, all requests are handled by service.ProcessRequest(), which is (mostly) independent of transport.

Persistence is mostly pluggable. See storage/ for examples.

Conclusion

Take a look at the doc/Manual.md for more information.