Skip to content
You are viewing docs for v0.1.0. Switch to latest

Inspect Demo

Demonstrates INSPECT DECISION to simulate and verify decision behavior without persisting any events. Test your business logic before it touches a stream.

A simple banking system with account opening, deposits, and guarded withdrawals.

CREATE AGGREGATE BankAccount;
CREATE COMMAND OpenAccount (
account_id STRING,
owner STRING,
initial_balance DECIMAL
);
CREATE COMMAND DepositFunds (
account_id STRING,
amount DECIMAL
);
CREATE COMMAND WithdrawFunds (
account_id STRING,
amount DECIMAL
);
CREATE EVENT AccountOpened (
owner STRING,
initial_balance DECIMAL
);
CREATE EVENT FundsDeposited (
amount DECIMAL
);
CREATE EVENT FundsWithdrawn (
amount DECIMAL
);
CREATE DECISION OpenAccount
FOR BankAccount
ON COMMAND OpenAccount
EMIT AS
SELECT EVENT AccountOpened (
owner := :owner,
initial_balance := :initial_balance
);
CREATE DECISION DepositFunds
FOR BankAccount
ON COMMAND DepositFunds
EMIT AS
SELECT EVENT FundsDeposited (
amount := :amount
);
CREATE DECISION WithdrawFunds
FOR BankAccount
ON COMMAND WithdrawFunds
STATE AS
SELECT
COALESCE(SUM(
CASE
WHEN event_type = 'AccountOpened' THEN data.initial_balance
WHEN event_type = 'FundsDeposited' THEN data.amount
WHEN event_type = 'FundsWithdrawn' THEN -data.amount
ELSE 0
END
), 0) AS balance
FROM DeReg."BankAccount$Events"
WHERE stream_id = :account_id
EMIT AS
SELECT EVENT FundsWithdrawn (
amount := :amount
)
WHERE balance >= :amount;

INSPECT DECISION — Simulate Account Opens

Section titled “INSPECT DECISION — Simulate Account Opens”

Prepare test commands as a table, then simulate the decision. No events are persisted:

CREATE TABLE test_opens AS VALUES
('ACC-001', 'Alice', 500.00),
('ACC-002', 'Bob', 200.00),
('ACC-003', 'Carol', 1000.00);
INSPECT DECISION OpenAccount
FROM test_opens
INTO simulated_open_events;
INSPECT DECISION3 event(s) emitted, 0 rejected → simulated_open_events

Review what would have happened:

SELECT stream_id, event_type, data FROM simulated_open_events;
+-----------+---------------+----------------------------------------------------------+
| stream_id | event_type | data |
+-----------+---------------+----------------------------------------------------------+
| ACC-001 | AccountOpened | {initial_balance: 500.000000000000000000, owner: Alice} |
| ACC-002 | AccountOpened | {initial_balance: 200.000000000000000000, owner: Bob} |
| ACC-003 | AccountOpened | {initial_balance: 1000.000000000000000000, owner: Carol} |
+-----------+---------------+----------------------------------------------------------+

Three accounts would be opened — but nothing was written to the event store.

CREATE TABLE test_deposits AS VALUES
('ACC-001', 150.00),
('ACC-002', 50.00);
INSPECT DECISION DepositFunds
FROM test_deposits
INTO simulated_deposit_events;
INSPECT DECISION2 event(s) emitted, 0 rejected → simulated_deposit_events
SELECT stream_id, event_type, data FROM simulated_deposit_events;
+-----------+----------------+----------------------------------+
| stream_id | event_type | data |
+-----------+----------------+----------------------------------+
| ACC-001 | FundsDeposited | {amount: 150.000000000000000000} |
| ACC-002 | FundsDeposited | {amount: 50.000000000000000000} |
+-----------+----------------+----------------------------------+

INSPECT DECISION — Simulate Guarded Withdrawals

Section titled “INSPECT DECISION — Simulate Guarded Withdrawals”

INSPECT runs against the real event store for STATE AS queries. First, create a real account with a known balance:

EXECUTE OpenAccount(account_id := 'ACC-100', owner := 'Dave', initial_balance := 300.00);
AccountOpened
stream_id: ACC-100
seq: 1
owner: Dave
initial_balance: 300
EXECUTE DepositFunds(account_id := 'ACC-100', amount := 200.00);
FundsDeposited
stream_id: ACC-100
seq: 2
amount: 200

Now simulate two withdrawals — one within balance, one exceeding it:

CREATE TABLE test_withdrawals AS VALUES
('ACC-100', 100.00),
('ACC-100', 600.00);
INSPECT DECISION WithdrawFunds
FROM test_withdrawals
INTO simulated_withdrawals;
INSPECT DECISION1 event(s) emitted, 1 rejected → simulated_withdrawals

The summary line tells you immediately: 1 accepted, 1 rejected. Query the details:

SELECT stream_id, event_type, data FROM simulated_withdrawals;
+-----------+----------------+----------------------------------+
| stream_id | event_type | data |
+-----------+----------------+----------------------------------+
| ACC-100 | FundsWithdrawn | {amount: 100.000000000000000000} |
| ACC-100 | __REJECTED__ | {amount: 600.000000000000000000} |
+-----------+----------------+----------------------------------+
  • Row 1: FundsWithdrawn — balance (500) >= amount (100) ✓
  • Row 2: __REJECTED__ — balance (500) < amount (600) ✗

The real balance is unchanged — INSPECT didn’t write anything:

SELECT * FROM DeReg."AccountBalances";
+------------+-------+
| account_id | owner |
+------------+-------+
| ACC-100 | Dave |
+------------+-------+
  • INSPECT DECISION simulates decisions without side effects
  • Table-driven testing — prepare inputs as tables, inspect outputs
  • Rejection tracking__REJECTED__ rows show which commands would fail and why
  • Summary outputN event(s) emitted, M rejected gives instant feedback
  • Real state, simulated commands — STATE AS queries run against the real event store, but no new events are written
  • CI-friendly — these scripts can run in pipelines to validate decision logic before deployment