Developers define records for stored state, commands for requested actions and events for business changes. Rust macros generate data-format checks and typed accessors from these definitions. Handlers implement the rules; the engine provides replication, logging and recovery.
#[record(kind = 1, record_len = 64,
uk(id = 0, fields = [account_id]))]
pub struct Account {
#[field(index = 1, immutable = true)]
pub account_id: u64,
#[field(index = 2)]
pub total_credit: Decimal<6>,
#[field(index = 3)]
pub total_debit: Decimal<6>,
}
#[command(kind = 1)]
pub struct Deposit {
#[field(index = 1)]
pub account_id: u64,
#[field(index = 2)]
pub amount: Decimal<6>,
}The model uses fixed-point decimals with six fractional digits and a unique key on account ID. The excerpt is partial and does not compile on its own. Definitions are from the current Bank v1.1 schema.
Deposit as a single business transaction
For a deposit of 100.000000, the handler changes both the customer and platform accounts within the same command transaction:
Each side emits a BalanceChanged event reporting the account’s updated totals; events can be returned with the response, subject to configured size limits. Non-positive amounts, deposits into the platform account and arithmetic overflow are rejected, leaving both accounts unchanged.
let account_id = command.account_id();
let amount = command.amount();
if account_id == PLATFORM_ACCOUNT_ID {
return Err(BankV2Error::PlatformDeposit);
}
if amount <= ZERO_AMOUNT {
return Err(BankV2Error::NonPositiveAmount);
}
// Both changes happen in the same transaction.
// adjust_account uses checked_add internally.
let (account_credit, account_debit) =
Self::adjust_account(account_id, tx, amount, ZERO_AMOUNT, true)?;
let (platform_credit, platform_debit) =
Self::adjust_account(
PLATFORM_ACCOUNT_ID, tx, ZERO_AMOUNT, amount, false,
)?;
Self::emit_balance_changed(tx, account_id, account_credit, account_debit);
Self::emit_balance_changed(
tx, PLATFORM_ACCOUNT_ID, platform_credit, platform_debit,
);
Ok(())Validate structure before executing business logic
Generated validation inspects the entire request: field lengths, allowed values, fixed-width encoding and trailing bytes. Both modes reject malformed requests before execution; handlers enforce business rules such as valid amounts and balances.
Define data and business rules
Developers define the data model, implement handlers and write tests; the engine supplies data access, transaction execution and cluster management. Business-invariant checks are in development.
The same model runs in the real cluster
Bank Deposit runs through the same network and replication path as a deployed cluster. Tests cover three separate node processes, lost responses and recovery after a process crash.
Explicit inputs keep execution replayable
Plugins must execute deterministically. External I/O, unconstrained randomness and machine-local time must not silently influence results. External facts enter the core as replayable inputs.
Record revisions
The example starts with the customer account absent and the platform account already at revision 1992 (credit = 0, debit = 5,000), then applies two successful deposits and one rejected zero-amount deposit. Amounts, IDs and revision numbers are illustrative; the revision rules below are the engine’s actual behavior: new records start at revision 1, each transaction that modifies a record increments its revision once, and a business rejection leaves records unchanged.
| Command / Outcome | Customer Account(42) | Platform Account(0) | Events returned with the result |
|---|---|---|---|
| Deposit(100) Success | Absent → revision 1 credit = 100, debit = 0 | revision 1992 → revision 1993 credit = 0, debit = 5,100 | Two BalanceChanged events New cumulative values for both accounts |
| Deposit(25) Success | revision 1 → revision 2 credit = 125, debit = 0 | revision 1993 → revision 1994 credit = 0, debit = 5,125 | Two new BalanceChanged events From this committed, applied execution |
| Deposit(0) Business rejection | Still revision 2 credit = 125, debit = 0 | Still revision 1994 credit = 0, debit = 5,125 | Business rejection No account-change events |
A record revision tracks changes to a single record and is distinct from the schema version, plugin version and global tx_seq. Within a transaction, all edits to one record produce a single revision; records that are not modified keep theirs.
Deposit sequence: simulate → verify → commit → apply → respond
Events returned with responses
The first Deposit’s successful response can include both account changes:
- Account 42: total credit 100, total debit 0.
- Account 0: total credit 0, total debit 100.
Both events belong to the same transaction. The caller sees the updated totals immediately, without a second query.
Record revisions and events serve different purposes
A revision identifies a version of a record. An event returned with the response describes a business change. BalanceChanged currently contains the account and cumulative amounts but no revision field.
An audit trail should link records, their before and after revisions, commands and plugin versions. Responses are subject to event-count and size limits and may be truncated or lost; complete auditing requires separate archival.