Transaction Semantics Reference

Overview

This page focuses on the current behavioral semantics of the Juice transaction APIs so that you can choose the right pattern in the service layer.

Terminology:

  • Manual transaction: engine.Tx() or engine.ContextTx(...) with explicit Begin/Commit/Rollback.

  • Functional transaction: juice.Transaction(...) or juice.NestedTransaction(...).

  • Current transaction context: the manager in ctx is already a TxManager.

Semantics Matrix

API

Start condition

Behavior on success

Behavior on failure

Typical scenario

engine.Tx() + Begin

Begin must be called manually

Commits after explicit Commit

Rolls back after explicit Rollback or an error

Infrastructure-level code that needs fine-grained control

engine.ContextTx(ctx, opts)

Same as above, but accepts sql.TxOptions

Same as above

Same as above

Transactions with a specific isolation level or read-only flag

juice.Transaction(ctx, handler, opts...)

ctx must carry a *juice.Engine

Commits when handler returns nil

Rolls back when handler returns a non-nil error

A unified transaction boundary in the service layer

juice.NestedTransaction(ctx, handler, opts...)

Reuses the current transaction if one exists, otherwise opens a new one

When reusing, the outer transaction commits; otherwise it follows Transaction

When reusing, the error bubbles to the outer layer; otherwise it rolls back

Composed services that should not repeatedly open transactions

Behavior Details

1) Context requirements for ``Transaction``

juice.Transaction extracts the manager from ctx and requires it to be a *juice.Engine. Otherwise it returns an error.

Common mistakes:

  • The manager was not injected into ctx with juice.ContextWithManager.

  • A context that is already inside a transaction is passed back into Transaction directly. In that case the manager is usually not a *juice.Engine.

Recommendation:

  • Use Transaction at the outer entry point.

  • Use NestedTransaction for reuse in inner layers.

2) ``NestedTransaction`` is not a savepoint

The semantics of NestedTransaction are “join when a transaction exists, create one otherwise”. It is not a database SAVEPOINT abstraction.

That means:

  • Reusing an outer transaction does not create an independent inner commit point.

  • An inner failure affects whether the outer transaction can commit, and should usually be returned upward.

3) Special error ``tx.ErrCommitOnSpecific``

When handler returns tx.ErrCommitOnSpecific, the transaction still attempts to commit and returns that error together with any commit error.

This is intended for special cases where a specific commit path must be marked explicitly. It is usually not something normal business code should depend on.

How to Choose in Practice

  • Choose manual transactions when you need full lifecycle control.

  • Prefer functional transactions when a business function is the transaction boundary.

  • When service A calls service B and both may also be called independently, use outer Transaction plus inner NestedTransaction.

  • If you need savepoint-level semantics, wrap them explicitly on the business side according to your database capabilities.