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