How does EK9 prevent partial transaction commits?
← Dependency Injection · Ref: Q338
EK9 prevents partial commits through three structural mechanisms: operator close auto-cleanup, no early return, and the isCommitted() safety check.
MECHANISM 1: OPERATOR CLOSE AUTO-CLEANUP
Transaction extends Closeable. When used with try-with-resources, operator close is called automatically when the scope exits. Implementations check isCommitted() in close and rollback if not committed. This prevents the 'forgot to rollback' bug.
MECHANISM 2: NO EARLY RETURN
EK9 has no return statement. You cannot return early from the middle of a transaction, which is one of the most common causes of partial commits in other languages. The transaction scope always runs to completion or throws.
MECHANISM 3: isCommitted() SAFETY CHECK
The isCommitted() method returns a tri-state Boolean:
- Unset: transaction state unknown (default, not yet committed or rolled back)
- True: committed
- False: explicitly not committed (active transaction)
Operator close can check this and take appropriate action.
COMMON PARTIAL COMMIT SCENARIOS PREVENTED
1. EXCEPTION BETWEEN OPERATIONS: operator close triggers rollback automatically
2. EARLY RETURN: impossible in EK9 (no return statement)
3. FORGOT TO COMMIT: operator close detects uncommitted state
4. FORGOT TO ROLLBACK: operator close handles cleanup
SPRING SILENT COMMIT BUG
In Spring, checked exceptions silently commit the transaction. A method that throws IOException commits partial data. EK9's operator close checks commit state regardless of exception type.
See Q333 for try-with-resources pattern. See Q144 for no break/continue/return. See Q134 for exception handling. See Q137 for try-with-resources basics.
Example
defines module qa.di.transaction.no.partial defines class SafeTransaction with trait of Transaction identifier <- String() committed <- false operations <- 0 SafeTransaction() -> identifier as String this.identifier: identifier recordOperation() -> description as String <- status as String: `${identifier} op#${operations}: ${description}` operations: operations + 1 override commit() committed: true override rollback() committed: false operations: 0 override isCommitted() as pure <- rtn as Boolean: committed <?- Safety net: auto-rollback if not committed. -?> override operator close as pure stdout <- Stdout() if committed stdout.println(`${identifier}: close after commit (${operations} ops)`) else stdout.println(`${identifier}: ROLLBACK on close (${operations} uncommitted ops)`) override operator ? as pure <- rtn <- true defines program NoPartialCommitDemo() stdout <- Stdout() // === SUCCESSFUL: all operations committed === stdout.println("=== Committed transaction ===") try -> txn <- SafeTransaction("TXN-A") status1 <- txn.recordOperation("debit account") stdout.println(status1) status2 <- txn.recordOperation("credit account") stdout.println(status2) txn.commit() catch -> ex as Exception stdout.println("Error: " + $ex) // === UNCOMMITTED: operator close auto-rollbacks === stdout.println("=== Uncommitted transaction ===") try -> txn <- SafeTransaction("TXN-B") status3 <- txn.recordOperation("update inventory") stdout.println(status3) status4 <- txn.recordOperation("send notification") stdout.println(status4) // No commit — operator close detects and reports catch -> ex as Exception stdout.println("Error: " + $ex) stdout.println("Operator close prevents partial commits automatically")
Common mistakes
E08180 — Class fields must be initialized at declaration. Without initialization or the injection suffix '!', the compiler reports a field-not-initialized error. See ek9 -h E08180 for details.
Incorrect:
identifier as String
Correct:
identifier <- String()
Other ways to ask this
- How do I ensure all-or-nothing transactions in EK9?
- What prevents half-committed data in EK9?
- How does EK9 handle transaction atomicity?
Coming from another language?
Java Spring: checked exceptions silently commit, @Transactional(rollbackFor) needed for explicit control. Go: must remember defer tx.Rollback() after every tx.Begin(). Rust: Drop trait ensures cleanup but requires explicit commit. C#: TransactionScope.Complete() must be called explicitly. Python: context manager __exit__ handles cleanup. EK9: operator close auto-cleanup, no return prevents mid-transaction exit, isCommitted() tri-state check, structural prevention of partial commits.
Keywords: safety, atomicity, return, rollback, prevent, migrate, dependency, cleanup, commit, partial, inject, close, structural