Files
Flash5/flash-extensions/flash-ext-data-core/docs/README.md
T
Zakaria El OrcheandClaude Opus 5 0194470c1f feat(data): actually invoke TxSynchronization.beforeCommit, and document the contract
beforeCommit(boolean) has been part of the TxSynchronization API from the
start and was invoked by nothing, in either manager — anyone implementing it
got silence, the same failure class as the dropped afterCommit callbacks in
the previous commit. Left out of that one because fixing it is a design
decision rather than a restored behaviour; this is that decision, written
down.

It now runs immediately before the real commit, with the transaction still
active and its session/connection still bound, which is the whole reason to
have a hook on this side of the commit: it can still write through the same
resource and land in the same atomic unit. Skipped when the transaction is
already rollback-only, since there is no commit to precede.

Throwing from it vetoes the commit: the transaction rolls back, the surviving
callbacks hear ROLLED_BACK, and the exception propagates. Without that, a hook
running before the commit would be strictly less useful than one running
after. A commit that fails on its own now takes the same path instead of
completing silently with no callback at all, and a rollback that also fails is
attached as a suppressed exception rather than replacing the one that explains
the failure.

Documented on the interface itself and in flash-ext-data-core/docs/README.md:
which hook sits on which side of the commit, what each may still touch, what
throwing does, and the per-transaction scoping rule from the previous commit.

Tests: 6 more (3 per manager) — runs inside the transaction with the resource
still bound, receives the read-only flag, skipped on a rollback-only
transaction, and vetoes the commit when it throws. 37 across the two managers
now, all green, reactor verify passes the 80% Jacoco gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 23:33:05 +00:00

5.2 KiB

flash-ext-data-core

Core comune per il layer dati di Flash.

Scopo

Questo modulo definisce il contratto transazionale condiviso tra le implementazioni backend. Non parla con Hibernate o JDBC direttamente: espone solo astrazioni e un runtime minimale.

Componenti

  • TxDefinition: metadata immutabile della transazione.
  • TxStatus: stato runtime restituito dal manager.
  • TxManager: contratto per begin, commit, rollback.
  • Tx: orchestration runtime e stack transazionale per thread.
  • ResourceRegistry: storage thread-local di risorse e synchronizations.
  • Repository<T, ID>: base repository auto-transazionale.
  • Spec<T>: predicato componibile.
  • Query<T>: oggetto query con spec, sort e paging.
  • SpecBuilder<T>: DSL fluente per costruire spec tipizzate.
  • RepositorySupport<T, ID>: helper interno condiviso.
  • TransactionPropagation: semantica di propagazione.
  • TransactionIsolation: livello di isolamento.
  • TxSynchronization: hook lifecycle (vedi sotto).

Modello di esecuzione

Il flusso è:

  1. Tx.call(definition, work) chiama TxManager.begin(definition).
  2. Il TxManager crea un TxStatus backend-specific.
  3. Lo status viene pushato nello stack thread-local.
  4. Il lavoro usa Tx.resource(Class) per ottenere la risorsa corrente.
  5. A fine lavoro Tx decide tra commit e rollback.
  6. Lo stack viene poppato e il thread-local viene pulito se vuoto.

Propagation supportata

  • REQUIRED: usa la tx attiva oppure ne apre una nuova.
  • REQUIRES_NEW: sospende la tx corrente e apre una nuova tx.
  • SUPPORTS: se esiste una tx attiva si aggancia, altrimenti esegue senza tx.
  • NOT_SUPPORTED: sospende la tx corrente ed esegue senza tx.
  • MANDATORY: richiede una tx attiva.

Synchronization (TxSynchronization)

Hook sul ciclo di vita di una transazione, registrati con Data.afterCommit(...) (o direttamente con ResourceRegistry.addSynchronization(...)).

Ogni callback appartiene esattamente alla transazione più interna attiva al momento della registrazione, e scatta una volta sola quando quella transazione completa:

  • una tx interna joined (REQUIRED) non è una transazione a sé, quindi i callback registrati al suo interno aspettano il commit più esterno;
  • una tx REQUIRES_NEW lo è, quindi completarla fa scattare solo i propri callback e lascia in sospeso quelli della transazione esterna sospesa.

Posizione rispetto al commit

hook quando risorsa
beforeCommit(readOnly) subito prima del commit reale sessione/connection ancora bound, tx ancora attiva
afterCommit() / afterRollback() dopo il completamento risorsa già sganciata
afterCompletion(outcome) dopo i due precedenti risorsa già sganciata

beforeCommit è l'unico hook che può ancora scrivere sulla stessa risorsa e finire nella stessa unità atomica: flush di un buffer, riga di audit, valore derivato. Non viene eseguito se la transazione è già rollback-only, perché non c'è nessun commit da precedere.

I callback post-completamento girano invece a risorsa sganciata: uno che apre una propria transazione ne ottiene una nuova invece di agganciarsi a quella appena conclusa. È questo che li rende il posto giusto per invalidare una cache, accodare un messaggio o notificare qualcosa fuori dal database.

Fallimenti

Lanciare da beforeCommit veta il commit: la transazione va in rollback, scattano afterRollback/afterCompletion(ROLLED_BACK) e l'eccezione arriva al chiamante. È il motivo per cui l'hook gira prima del commit e non dopo — può ancora rifiutare.

Gli hook post-completamento non hanno questo potere: la transazione è già chiusa quando girano, quindi un'eccezione si propaga ma non cambia nulla di ciò che è stato committato, e blocca i callback in coda dietro di lei.

Uso di Repository

Repository è la base comune per le repository concrete. Ogni operazione pubblica usa internamente una tx REQUIRED o REQUIRED read-only.

Le sottoclassi implementano i metodi doXxx(...) del nuovo modello:

  • doFind(Query<T>)
  • doFindOne(Spec<T>)
  • doFindPage(Query<T>)
  • doDeleteAll(Spec<T>)
  • doUpdateAll(Spec<T>, T)

I vecchi overload di findAll(...) e findPage(...) sono stati ridotti a una combinazione di Query<T> e Spec<T>.

public abstract class Repository<T, ID> {
    protected Repository(Tx tx) { ... }
    protected final <R> R tx(Tx.TxCallable<R> work) { ... }
}

Composizione con Flash

DataExtension registra:

  • Tx nel FlashContext
  • TxManager nel FlashContext
  • un annotation processor per @Transactional

Questo rende il layer dati componibile con il sistema di extension di Flash senza stato globale.

Note implementative

  • Lo stack transazionale è thread-local e viene ripulito quando torna vuoto.
  • Le risorse backend sono sospese e ripristinate per REQUIRES_NEW e NOT_SUPPORTED.
  • TxSynchronization è il punto di aggancio per hook di commit/rollback/completion.
  • Le synchronization sono in una lista thread-local; ogni transazione nuova registra quante ne esistevano già alla sua apertura e fa scattare solo la propria coda, così una REQUIRES_NEW non trascina con sé quelle della transazione sospesa.