Rule Cascade
LearnThe process, end to end

Load in applications

Embed a runtime and load the bundle at start-up, or let the application ask the rule server. Either way, log the checksum.

An application gets rules in one of two ways. Embedded: it loads the bundle into a runtime in its own process and evaluates with a function call. Rule server: it sends each request over HTTP to a server that holds the rules. Embedded is faster and has no network hop; the rule server suits a language without a runtime and keeps one place to update.

Hands-on

  1. Embedded. Load the bundle once, at start-up. A bundle that cannot be used throws, so the service never starts with bad rules. This is the Node.js example service:

    examples/backend-node/src/app.mjs
    /** Reads a precompiled bundle. A bad bundle throws here, so the service never starts with it. */
    export function loadRules(bundlePath) {
      return RuleSet.fromBundle(JSON.parse(readFileSync(bundlePath, 'utf8')));
    }
    examples/backend-node/src/main.mjs
    const rules = loadRules(bundle); // throws, and the process stops, when the bundle is not usable
    const { server } = createApp({
      rules,
      publish: (command) => console.log(`command ${command.name} -> ${command.ref ?? command.type}`, JSON.stringify(command.payload)),
    });
    server.listen(port, () => console.log(`${rules.id}@${rules.version} (${rules.checksum}) on :${port}`));

    The service logs the ruleset id, version and checksum it loaded. Java, Go, Python and the other languages do the same: see Use it in your language.

  2. In the browser. Fetch the client manifest from the rule server. The manifest client keeps it and revalidates it with the ETag:

    packages/typescript/README.md
    const manifests = createManifestClient({ baseUrl: '/api/rules' });
    const manifest = await manifests.get('acme.payments.transfer');   // cached; revalidated with the ETag
  3. Rule server. Send the request with the ruleset id. The answer carries the checksum of the rules that decided:

    POST /evaluations (real exchange)
    > POST /evaluations
    > Authorization: Bearer learn-demo-token
    < 200
    {
      "ruleset": "shop.orders",
      "version": "1.0.0",
      "checksum": "sha256:4583c90bbfcacb54bf78e11d9e8190ecc086ce4b69aade609b7a0928d452ea82",
      "decision": "deny",
      "findings": [
        {
          "rule": "order.quantity.max",
          "code": "SHOP-ORD-001",
          "severity": "error",
          "message": "You can order at most 10 items.",
          "fields": [
            "/quantity"
          ],
          "blocking": true,
          "status": "open",
          "resolution": "none",
          "source": "shop.orders@1.0.0"
        }
      ],
      "effects": [],
      "commands": []
    }
    
    > POST /evaluations
    > Authorization: Bearer learn-demo-token
    < 200
    {
      "ruleset": "shop.orders",
      "version": "1.0.0",
      "checksum": "sha256:4583c90bbfcacb54bf78e11d9e8190ecc086ce4b69aade609b7a0928d452ea82",
      "decision": "allow",
      "findings": [],
      "effects": [],
      "commands": [
        {
          "name": "order.placed",
          "type": "event",
          "rule": "order.placed",
          "idempotencyKey": "order.placed:o-4",
          "payload": {
            "orderId": "o-4",
            "quantity": 3
          },
          "ref": "OrderPlaced"
        }
      ]
    }

Done when

  • The application refuses to start when the bundle cannot be loaded.
  • Its start-up log names the ruleset id, version and checksum.
  • An evaluation answer carries the same checksum.

Go deeper

Course overview

On this page