Overview
Learn what DeQL is and the core philosophy behind it.
DEQL lets you model CQRS‑ES systems as specifications, not scattered implementation details.
✅ Executable decisions
✅ First‑class inspection of events and aggregates
✅ Disposable, replayable projections
Without DeQL
With DeQL
WHERE balance >= :amount)INSPECT lets you simulate decisions and projections without side effectsINSPECT — no infrastructure neededLet’s build a real CQRS-ES system from scratch. We’ll model an Employee domain that handles hiring and promotions.
First, define the aggregate boundary and the commands it accepts:
CREATE AGGREGATE Employee;
CREATE COMMAND HireEmployee ( employee_id STRING, name STRING, grade STRING);
CREATE COMMAND PromoteEmployee ( employee_id STRING, new_grade STRING);Register the events these commands can produce:
CREATE EVENT EmployeeHired ( name STRING, grade STRING);
CREATE EVENT EmployeePromoted ( new_grade STRING);Now wire up the business logic as decisions. Hiring is unconditional, but promotions are guarded — you can’t promote someone to the same grade:
CREATE DECISION HireFOR EmployeeON COMMAND HireEmployeeEMIT AS SELECT EVENT EmployeeHired ( name := :name, grade := :grade );
CREATE DECISION PromoteFOR EmployeeON COMMAND PromoteEmployeeSTATE AS SELECT LAST( CASE WHEN event_type = 'EmployeeHired' THEN data.grade WHEN event_type = 'EmployeePromoted' THEN data.new_grade ELSE NULL END ) AS current_grade FROM DeReg."Employee$Events" WHERE stream_id = :employee_idEMIT AS SELECT EVENT EmployeePromoted ( new_grade := :new_grade ) WHERE :new_grade <> current_grade;Add read-side projections — a new-hire report and a promotions report:
CREATE PROJECTION NewHireReport ASSELECT stream_id AS employee_id, LAST(data.name) AS name, LAST(data.grade) AS hired_gradeFROM DeReg."Employee$Events"WHERE event_type = 'EmployeeHired'GROUP BY stream_id;
CREATE PROJECTION PromotionsReport ASSELECT stream_id AS employee_id, seq, data.new_grade AS promoted_toFROM DeReg."Employee$Events"WHERE event_type = 'EmployeePromoted'ORDER BY employee_id, seq;The system is ready. Execute some commands:
EXECUTE HireEmployee(employee_id := 'EMP-001', name := 'Alice', grade := 'L4');
✓ EmployeeHired stream_id: EMP-001 seq: 1 name: Alice grade: L4EXECUTE PromoteEmployee(employee_id := 'EMP-001', new_grade := 'L5');
✓ EmployeePromoted stream_id: EMP-001 seq: 2 new_grade: L5Try promoting to the same grade again — the decision guard rejects it:
EXECUTE PromoteEmployee(employee_id := 'EMP-001', new_grade := 'L5');
✗ REJECTED decision: Promote guard: :new_grade <> current_grade state: current_grade = 'L5' command: employee_id = 'EMP-001' command: new_grade = 'L5'Query the projections:
SELECT * FROM DeReg."NewHireReport" ORDER BY employee_id;
+-------------+-------+-------------+ | employee_id | name | hired_grade | +-------------+-------+-------------+ | EMP-001 | Alice | L4 | +-------------+-------+-------------+
SELECT * FROM DeReg."PromotionsReport";
+-------------+-----+-------------+ | employee_id | seq | promoted_to | +-------------+-----+-------------+ | EMP-001 | 2 | L5 | +-------------+-----+-------------+That’s a full CQRS-ES system — aggregate, commands, events, guarded decisions, and projections — all declared, no boilerplate.
Want a simpler, repeatable process? Look at templates.
One template. One line. A fully operational event-sourced system.
APPLY TEMPLATE wallet_aggregateWITH (wallet_name = 'Main', currency = 'USD');That single line expands into a fully functional system:
MainWallet with typed state (wallet_id, currency, balance)TopUpMain and DebitMain expressing caller intentMainWalletToppedUp and MainWalletDebited as immutable factsTopUpMain (unconditional credit) and DebitMain (guarded: WHERE balance >= :amount)MainWalletBalance read model auto-generated with the same fields as the aggregateSpin up more wallets in one line each:
APPLY TEMPLATE wallet_aggregateWITH (wallet_name = 'Promo', currency = 'USD');
APPLY TEMPLATE wallet_aggregateWITH (wallet_name = 'Roaming', currency = 'USD');
APPLY TEMPLATE wallet_aggregateWITH (wallet_name = 'CorporatePool', currency = 'USD');Four aggregates, eight commands, eight events, eight decisions — zero boilerplate.
Send a command:
EXECUTE TopUpMain(wallet_id := 'wal-001', amount := 100.00);
✓ MainWalletToppedUp stream_id: wal-001 seq: 1 amount: 100.00 balance_after: 100.00Query the projection:
SELECT * FROM DeReg.MainWalletBalance;
wallet_id | currency | balance ----------|----------|-------- wal-001 | USD | 100.00Inspect before you ship:
CREATE TABLE test_topups ASVALUES ('wal-001'::UUID, 100.00);
INSPECT DECISION TopUpMainFROM test_topupsINTO simulated_events;
INSPECT PROJECTION MainWalletBalanceFROM simulated_eventsINTO simulated_balances;
SELECT * FROM simulated_balances;Inspection runs in production or any environment without altering domain facts.
Overview
Learn what DeQL is and the core philosophy behind it.
Language Reference
Explore the full language reference — aggregates, commands, events, decisions, projections, templates, and more.
Two-Phase Model
Understand the two-phase model — definitions then decision assembly.
Examples
See complete working systems: Inventory, Registry, Telecom Wallet.