docs(data): write the data-layer docs in English

The three data modules' docs were in Italian, so the synchronization contract
added in the previous commit went in as Italian too, to match its file. English
is the project's language for docs, comments and READMEs alike, and a file half
in each is worse than either — so all three are translated, not just the new
section.

Content is otherwise unchanged, except the "synchronizations run on
commit/rollback" line in the two backend READMEs, which was vague before and is
now accurate about which hook sees the session/connection still bound, pointing
at flash-ext-data-core's README for the full contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Zakaria El Orche
2026-08-12 23:36:11 +00:00
co-authored by Claude Opus 5
parent 0194470c1f
commit c5179146c0
3 changed files with 152 additions and 143 deletions
@@ -1,93 +1,93 @@
# flash-ext-data-core
Core comune per il layer dati di Flash.
Shared core for Flash's data layer.
## Scopo
## Purpose
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.
This module defines the transactional contract shared across backend implementations. It does not
talk to Hibernate or JDBC directly: it exposes abstractions and a minimal runtime, nothing else.
## Componenti
## Components
- `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).
- `TxDefinition`: immutable transaction metadata.
- `TxStatus`: runtime state returned by the manager.
- `TxManager`: the `begin`/`commit`/`rollback` contract.
- `Tx`: runtime orchestration and the per-thread transaction stack.
- `ResourceRegistry`: thread-local storage for resources and synchronizations.
- `Repository<T, ID>`: self-transactional base repository.
- `Spec<T>`: composable predicate.
- `Query<T>`: query object carrying spec, sort and paging.
- `SpecBuilder<T>`: fluent DSL for building typed specs.
- `RepositorySupport<T, ID>`: shared internal helper.
- `TransactionPropagation`: propagation semantics.
- `TransactionIsolation`: isolation level.
- `TxSynchronization`: lifecycle hooks (see below).
## Modello di esecuzione
## Execution model
Il flusso è:
The flow is:
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.
1. `Tx.call(definition, work)` calls `TxManager.begin(definition)`.
2. The `TxManager` creates a backend-specific `TxStatus`.
3. The status is pushed onto the thread-local stack.
4. The work uses `Tx.resource(Class)` to obtain the current resource.
5. When the work ends, `Tx` chooses between `commit` and `rollback`.
6. The stack is popped, and the thread-local is cleared once it is empty.
## Propagation supportata
## Supported propagation
- `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.
- `REQUIRED`: use the active transaction, or open a new one.
- `REQUIRES_NEW`: suspend the current transaction and open a new one.
- `SUPPORTS`: join the active transaction if there is one, otherwise run without a transaction.
- `NOT_SUPPORTED`: suspend the current transaction and run without one.
- `MANDATORY`: require an active transaction.
## Synchronization (`TxSynchronization`)
## Synchronizations (`TxSynchronization`)
Hook sul ciclo di vita di **una** transazione, registrati con `Data.afterCommit(...)` (o
direttamente con `ResourceRegistry.addSynchronization(...)`).
Lifecycle hooks for **one** transaction, registered through `Data.afterCommit(...)` (or directly
with `ResourceRegistry.addSynchronization(...)`).
Ogni callback appartiene esattamente alla transazione più interna attiva al momento della
registrazione, e scatta una volta sola quando *quella* transazione completa:
Every callback belongs to exactly the innermost transaction active at registration time, and fires
exactly once, when *that* transaction completes:
- 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.
- a **joined** inner transaction (`REQUIRED`) is not a transaction of its own, so callbacks
registered inside one wait for the outermost commit;
- a `REQUIRES_NEW` transaction is, so completing it fires only its own callbacks and leaves the
suspended outer transaction's pending.
### Posizione rispetto al commit
### Which side of the commit each hook sits on
| hook | quando | risorsa |
| hook | when | resource |
| --- | --- | --- |
| `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(readOnly)` | immediately **before** the real commit | session/connection still **bound**, transaction still active |
| `afterCommit()` / `afterRollback()` | after completion | resource already **unbound** |
| `afterCompletion(outcome)` | after the two above | resource already unbound |
`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.
`beforeCommit` is the only hook that can still write through the same resource and have the write
land in the same atomic unit: flush a buffer, stamp an audit row, materialize a derived value. It
is skipped when the transaction is already `rollback-only`, since there is no commit to precede.
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.
Post-completion callbacks run with the resource unbound instead: one that opens its own transaction
gets a **fresh** one rather than joining the transaction that just finished. That is what makes
them the right place to refresh a cache, enqueue a message, or notify anything outside the
database.
### Fallimenti
### Failure
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.
Throwing from `beforeCommit` **vetoes the commit**: the transaction is rolled back,
`afterRollback`/`afterCompletion(ROLLED_BACK)` fire, and the exception reaches the caller. That is
the reason the hook runs before the commit rather than after — it can still refuse.
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.
The post-completion hooks have no such power: the transaction is already over by the time they run,
so an exception propagates but changes nothing already committed, and stops the callbacks queued
behind it.
## Uso di `Repository`
## Using `Repository`
`Repository` è la base comune per le repository concrete.
Ogni operazione pubblica usa internamente una tx `REQUIRED` o `REQUIRED` read-only.
`Repository` is the shared base for concrete repositories. Every public operation internally uses a
`REQUIRED` transaction, read-only where applicable.
Le sottoclassi implementano i metodi `doXxx(...)` del nuovo modello:
Subclasses implement the `doXxx(...)` methods:
- `doFind(Query<T>)`
- `doFindOne(Spec<T>)`
@@ -95,7 +95,8 @@ Le sottoclassi implementano i metodi `doXxx(...)` del nuovo modello:
- `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>`.
The old `findAll(...)` and `findPage(...)` overloads were reduced to a combination of `Query<T>` and
`Spec<T>`.
```java
public abstract class Repository<T, ID> {
@@ -104,21 +105,21 @@ public abstract class Repository<T, ID> {
}
```
## Composizione con Flash
## Composing with Flash
`DataExtension` registra:
`DataExtension` registers:
- `Tx` nel `FlashContext`
- `TxManager` nel `FlashContext`
- un annotation processor per `@Transactional`
- `Tx` in the `FlashContext`
- `TxManager` in the `FlashContext`
- an annotation processor for `@Transactional`
Questo rende il layer dati componibile con il sistema di extension di Flash senza stato globale.
This makes the data layer composable with Flash's extension system without global state.
## Note implementative
## Implementation notes
- 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.
- The transaction stack is thread-local and is cleared once it becomes empty.
- Backend resources are suspended and restored for `REQUIRES_NEW` and `NOT_SUPPORTED`.
- `TxSynchronization` is the hook point for commit/rollback/completion callbacks.
- Synchronizations live in a thread-local list; every new transaction records how many were already
registered when it opened and fires only its own tail, so a `REQUIRES_NEW` does not drag along
the suspended transaction's callbacks.
@@ -1,14 +1,15 @@
# flash-ext-data-hibernate
Backend Hibernate per `flash-ext-data-core`.
Hibernate backend for `flash-ext-data-core`.
## Scopo
## Purpose
Questo modulo implementa `TxManager` sopra `SessionFactory` e fornisce una base repository Hibernate-centric.
This module implements `TxManager` on top of a `SessionFactory` and provides a Hibernate-centric
repository base class.
## Come si usa
## How to use it
### 1. Creare il manager
### 1. Create the manager
```java
SessionFactory sessionFactory = ...;
@@ -16,12 +17,12 @@ HibernateTxManager txManager = new HibernateTxManager(sessionFactory);
DataExtension extension = new DataExtension(txManager);
```
### 2. Installare lestensione in Flash
### 2. Install the extension in Flash
Lestensione registra `Tx` e `TxManager` nel `FlashContext`.
Le handler class-based annotate con `@Transactional` vengono wrappate automaticamente.
The extension registers `Tx` and `TxManager` in the `FlashContext`. Class-based handlers annotated
with `@Transactional` are wrapped automatically.
### 3. Definire una repository
### 3. Define a repository
```java
public final class UserRepository extends HibernateRepository<User, Long> {
@@ -31,7 +32,7 @@ public final class UserRepository extends HibernateRepository<User, Long> {
}
```
Con il nuovo modello query/spec puoi esporre campi riusabili come costanti:
With the query/spec model you can expose reusable fields as constants:
```java
public final class UserRepository extends HibernateRepository<User, Long> {
@@ -48,7 +49,7 @@ public final class UserRepository extends HibernateRepository<User, Long> {
}
```
Le query domain-specific possono usare gli helper della base class:
Domain-specific queries can use the base class helpers:
```java
public List<User> findByEmailDomain(String domain) {
@@ -58,35 +59,37 @@ public List<User> findByEmailDomain(String domain) {
}
```
## Come funziona sotto
## How it works underneath
- La tx corrente è rappresentata da `HibernateTxStatus`.
- La risorsa esposta al core è una `Session`.
- `Tx.resource(Session.class)` recupera la `Session` dal contesto corrente.
- `REQUIRES_NEW` sospende lo status attivo e apre una nuova `Session`.
- `NOT_SUPPORTED` sospende la tx attiva e continua senza sessione bindata.
- The current transaction is represented by `HibernateTxStatus`.
- The resource exposed to the core is a `Session`.
- `Tx.resource(Session.class)` retrieves the `Session` from the current context.
- `REQUIRES_NEW` suspends the active status and opens a new `Session`.
- `NOT_SUPPORTED` suspends the active transaction and continues with no session bound.
## Repository base
## Repository base class
`HibernateRepository` fornisce:
`HibernateRepository` provides:
- `findById`, `findAll`, `findPage`, `findOne`
- `save`, `update`, `delete`, `saveAll`
- bulk `deleteAll(Spec<T>)` e `updateAll(Spec<T>, T)`
- helper HQL: `hql(...)`, `hqlMutate(...)`
- bulk `deleteAll(Spec<T>)` and `updateAll(Spec<T>, T)`
- HQL helpers: `hql(...)`, `hqlMutate(...)`
Le classi concrete devono solo implementare query di dominio, non il plumbing transazionale.
Concrete classes only have to implement domain queries, never the transactional plumbing.
## Semantica transazionale
## Transactional semantics
- `REQUIRED`: join o apertura nuova tx.
- `REQUIRES_NEW`: sospensione del contesto corrente.
- `SUPPORTS`: join se c’è tx, altrimenti no-op.
- `NOT_SUPPORTED`: sospende e prosegue senza tx.
- `MANDATORY`: fallisce se non c’è tx.
- `REQUIRED`: join, or open a new transaction.
- `REQUIRES_NEW`: suspend the current context.
- `SUPPORTS`: join if a transaction exists, otherwise no-op.
- `NOT_SUPPORTED`: suspend and continue without a transaction.
- `MANDATORY`: fail if there is no transaction.
## Note
## Notes
- `Session` viene chiusa a fine tx nuova.
- Le synchronizations vengono eseguite al commit/rollback.
- Il backend è pensato per essere usato tramite la base class, non direttamente.
- The `Session` is closed when a new transaction ends.
- Synchronizations registered in a transaction fire when *that* transaction completes:
`beforeCommit` while it is still active and the `Session` still bound, the post-completion hooks
once it is unbound. See `flash-ext-data-core/docs/README.md` for the full contract.
- This backend is meant to be used through the base class, not directly.
@@ -1,14 +1,15 @@
# flash-ext-data-jdbc
Backend JDBC per `flash-ext-data-core`.
JDBC backend for `flash-ext-data-core`.
## Scopo
## Purpose
Questo modulo implementa `TxManager` sopra `DataSource` e fornisce una base repository SQL raw.
This module implements `TxManager` on top of a `DataSource` and provides a raw-SQL repository base
class.
## Come si usa
## How to use it
### 1. Creare il manager
### 1. Create the manager
```java
DataSource dataSource = ...;
@@ -16,11 +17,12 @@ JdbcTxManager txManager = new JdbcTxManager(dataSource);
DataExtension extension = new DataExtension(txManager);
```
### 2. Installare lestensione in Flash
### 2. Install the extension in Flash
Come per Hibernate, `DataExtension` registra `Tx` nel `FlashContext` e abilita `@Transactional` sugli handler class-based.
As with Hibernate, `DataExtension` registers `Tx` in the `FlashContext` and enables
`@Transactional` on class-based handlers.
### 3. Definire una repository
### 3. Define a repository
```java
public final class UserRepository extends JdbcRepository<User, Long> {
@@ -35,7 +37,7 @@ public final class UserRepository extends JdbcRepository<User, Long> {
}
```
Anche qui puoi esporre `Spec` riusabili e comporre query dal service layer:
Here too you can expose reusable `Spec`s and compose queries from the service layer:
```java
public final class UserRepository extends JdbcRepository<User, Long> {
@@ -47,7 +49,7 @@ public final class UserRepository extends JdbcRepository<User, Long> {
}
```
Per il salvataggio e lupdate devi fornire il binding esplicito:
Saving and updating need an explicit binding:
```java
@Override
@@ -61,36 +63,39 @@ protected void bindInsert(PreparedStatement ps, User entity) throws SQLException
}
```
## Come funziona sotto
## How it works underneath
- La tx corrente espone una `Connection`.
- `Tx.resource(Connection.class)` recupera la connessione bindata al thread.
- `REQUIRES_NEW` sospende la connessione attiva e ne apre una nuova.
- `NOT_SUPPORTED` sospende il contesto e prosegue senza tx.
- The current transaction exposes a `Connection`.
- `Tx.resource(Connection.class)` retrieves the connection bound to the thread.
- `REQUIRES_NEW` suspends the active connection and opens a new one.
- `NOT_SUPPORTED` suspends the context and continues without a transaction.
## Repository base
## Repository base class
`JdbcRepository` fornisce:
`JdbcRepository` provides:
- query `select` con `queryOne`, `queryMany`
- mutation con `mutate`
- persistenza con `doSave`, `doUpdate`
- paging con `doFindPage`
- `select` queries through `queryOne`, `queryMany`
- mutations through `mutate`
- persistence through `doSave`, `doUpdate`
- paging through `doFindPage`
- bulk `deleteAll(Spec<T>)`
- helper raw `queryOne(...)`, `queryMany(...)`, `mutate(...)`
- raw helpers `queryOne(...)`, `queryMany(...)`, `mutate(...)`
Le repository concrete devono solo tradurre tra `ResultSet` e dominio.
Concrete repositories only have to translate between `ResultSet` and the domain.
## Semantica transazionale
## Transactional semantics
- `REQUIRED`: join o apertura nuova tx.
- `REQUIRES_NEW`: sospensione del contesto corrente.
- `SUPPORTS`: join se c’è tx, altrimenti no-op.
- `NOT_SUPPORTED`: sospende e prosegue senza tx.
- `MANDATORY`: fallisce se non c’è tx.
- `REQUIRED`: join, or open a new transaction.
- `REQUIRES_NEW`: suspend the current context.
- `SUPPORTS`: join if a transaction exists, otherwise no-op.
- `NOT_SUPPORTED`: suspend and continue without a transaction.
- `MANDATORY`: fail if there is no transaction.
## Note
## Notes
- La `Connection` viene chiusa a fine tx nuova.
- Le synchronizations vengono eseguite al commit/rollback.
- Se una repository usa `doDelete(T)`, il comportamento predefinito è non supportato: usare `deleteById` o override specifico.
- The `Connection` is closed when a new transaction ends.
- Synchronizations registered in a transaction fire when *that* transaction completes:
`beforeCommit` while it is still active and the `Connection` still bound, the post-completion
hooks once it is unbound. See `flash-ext-data-core/docs/README.md` for the full contract.
- If a repository uses `doDelete(T)`, the default behaviour is unsupported: use `deleteById` or
override it.