Which transaction management pattern should I use in EK9?
← Dependency Injection · Ref: Q339
EK9 offers five transaction patterns. Choose based on your specific requirements using this decision guide.
DEFAULT CHOICE: TRY-WITH-RESOURCES (Pattern 1)
Use for: most CRUD operations, single-function transaction scope
Why: most explicit, easiest to debug, AI generates correctly
Code: try -> txn <- Transaction() ... txn.commit()
CONTEXT PASSING (Pattern 2)
Use for: multiple functions need shared state beyond just the transaction
Why: typed context record carries transaction + audit log + correlation ID
When: call chains need rich context, not just the transaction
DELEGATION/DECORATOR (Pattern 3)
Use for: adding transactions to existing services without modification
Why: separation of concerns, composable, testable
When: layered architecture, multiple cross-cutting concerns
AOP ASPECT (Pattern 4)
Use for: truly uniform transaction behavior across many services
Caution: makes transactions invisible at call site
When: team understands AOP, behavior is identical for all advised methods
CALLBACK/HIGHER-ORDER (Pattern 5)
Use for: reusable transaction execution framework
Why: centralizes transaction management, any operation can use it
When: infrastructure code, many different operations need same wrapping
DYNAMIC FUNCTIONS (combine with any pattern)
Use for: preventing API pollution in deep call chains
Why: captures transaction in closure, keeps downstream APIs clean
When: transaction scope is wide but most functions do not need direct access
DECISION FLOWCHART
1. Is the transaction scope a single function? -> Pattern 1 (try-with-resources)
2. Do multiple functions need shared context? -> Pattern 2 (context record)
3. Are you wrapping existing services? -> Pattern 3 (delegation)
4. Is behavior truly uniform across all services? -> Pattern 4 (aspect, with caution)
5. Do you want a reusable framework? -> Pattern 5 (callback)
6. Is API pollution a concern? -> Add dynamic functions to any pattern
See Q333 for Pattern 1. See Q334 for Pattern 3. See Q335 for Pattern 5. See Q336 for dynamic functions. See Q337 for Pattern 4. See Q340 for AI-friendliness analysis.
Example
defines module qa.di.transaction.selection defines class MiniTransaction with trait of Transaction identifier <- String() committed <- false MiniTransaction() -> identifier as String this.identifier: identifier override commit() committed: true override isCommitted() as pure <- rtn as Boolean: committed override operator close as pure stdout <- Stdout() stdout.println(`${identifier} closed (committed=${committed})`) override operator ? as pure <- rtn <- true defines program PatternSelectionDemo() stdout <- Stdout() // === PATTERN 1: Try-with-resources (DEFAULT CHOICE) === stdout.println("=== Pattern 1: Try-with-resources ===") try -> txn <- MiniTransaction("P1") stdout.println("Business logic here") txn.commit() catch -> ex as Exception stdout.println("Error: " + $ex) // Most use cases are covered by Pattern 1 above // Only use other patterns when Pattern 1 doesn't fit stdout.println("Default: try-with-resources") stdout.println("Context passing: when multiple functions need shared state") stdout.println("Delegation: when wrapping existing services") stdout.println("Aspect: when behavior is truly uniform (use with caution)") stdout.println("Callback: when building reusable transaction framework")
Common mistakes
E08180 — Class fields must be initialized at declaration. The MiniTransaction class initializes all its fields inline. Omitting initialization without the '!' injection suffix is a compile-time error. See ek9 -h E08180 for details.
Incorrect:
identifier as String
Correct:
identifier <- String()
Other ways to ask this
- How do I choose between EK9 transaction patterns?
- What is the best transaction pattern for my use case?
- When should I use try-with-resources vs delegation vs aspects for transactions?
Coming from another language?
Spring: primarily @Transactional (AOP), TransactionTemplate (callback). Go: explicit transaction passing (context pattern). Rust: Diesel closure pattern (callback). C#: TransactionScope (ambient), middleware (delegation). Kotlin: transaction { } block (callback). EK9: five patterns from most-explicit to most-implicit, try-with-resources recommended as default, dynamic functions solve API pollution uniquely.
Keywords: inject, choose, guide, decision, selection, when, comparison, flowchart, dependency, recommendation, pattern, use