Transaction Creation Patterns
Three ways to obtain a
Transaction, depending on how long the work lives and whether it writes at all.
Status: โ Implemented ยท Visibility: Public ยท Level: ๐ข Start Here ยท Category: Transactions
๐ฏ What it solves
Every write or read against Typhon starts with a Transaction, but not every caller needs the same scaffolding
around it. A game tick wants one UnitOfWork shared by dozens of transactions with an explicit flush at the end; a
REPL command or a test wants a single transaction with zero ceremony; a query handler that never writes shouldn't
pay for a durability boundary it will never use. Picking the wrong pattern either adds boilerplate (manual UoW
management for a one-off write) or wastes a scarce resource (a UoW Registry slot held by a pure read). These three
entry points cover those cases without forcing every caller through the same full hierarchy.
โ๏ธ How it works (in brief)
All three patterns ultimately produce a Transaction pulled from the same pooled TransactionChain, so they share
identical MVCC semantics, TSN assignment, and auto-rollback-on-Dispose behavior. They differ only in who owns the
UnitOfWork โ the durability boundary โ and how much of it gets allocated. Standard creation gives the caller an
explicit UnitOfWork to reuse across many transactions. CreateQuickTransaction hides the UnitOfWork behind the
Transaction it returns, coupling their lifetimes. CreateReadOnlyTransaction skips the UnitOfWork entirely โ
no durability mode, no registry slot, no ChangeSet.
Sub-features
| Pattern | Entry point | UoW lifetime | Use when |
|---|---|---|---|
| Standard | dbe.CreateUnitOfWork(mode) then uow.CreateTransaction() |
Caller-managed, spans N transactions | Game tick, request batch, bulk import |
| Quick transaction | dbe.CreateQuickTransaction(mode) |
Disposed together with the one Transaction it owns |
Single-shot write, REPL, tests |
| Read-only transaction | dbe.CreateReadOnlyTransaction() |
None โ no UoW is allocated | Snapshot reads, query dispatch |
โ ๏ธ Guarantees & limits
- All three return the same
Transactiontype โ there is no read-only-specific or quick-specific subclass; the difference is entirely in how (or whether) aUnitOfWorkbacks it. - Every pattern is single-thread-affine and auto-rolls-back on
Dispose()if not explicitly committed โ see Transaction Lifecycle, Thread Affinity & Pooling for the shared lifecycle guarantees that apply regardless of which pattern created the transaction. - Choosing a pattern is a one-time decision per call site, not per transaction instance โ there is no API to
convert a
Transactionfrom one pattern to another after creation (e.g., a read-only transaction can never start writing). UnitOfWorkinstances are not pooled; onlyTransactioninstances are (TransactionChain, capacity 16).
๐งช Tests
- UnitOfWorkTests โ standard pattern
(
UoW_CreateTransaction_ReturnsValidTx,UoW_MultipleTransactions_ShareIdentity) and quick-transaction pattern (QuickTx_*) side by side - TransactionChainTests โ read-only pattern
(
ReadOnly_*): noUnitOfWork, throws on write, snapshot isolation
๐ Related
- Related features: Transaction Lifecycle, Thread Affinity & Pooling (full lifecycle guarantees), Unit of Work (the durability boundary itself), Durability Modes
- Sub-features: Standard (UnitOfWork + CreateTransaction), CreateQuickTransaction, CreateReadOnlyTransaction