Tentackle Persistence — The PDO-Aware Persistence Layer¶
Overview and Motivation¶
The tentackle-persistence module is where the two halves of Tentackle's architecture finally meet: the
Persistent Domain Object (PDO) abstraction from tentackle-pdo
and the raw, dialect-aware database machinery from tentackle-database. It
provides the concrete persistence implementation behind every PDO — the code that actually selects, inserts,
updates and deletes domain objects, manages their caches, locks, number ranges, security rules and the remoting
that lets all of this work transparently across JVMs.
In the PDO pattern (see pdo.md) a domain object is a dynamic proxy that combines two injected implementations:
- a domain implementation (business logic), and
- a persistence implementation (database logic).
This module supplies the base classes for the persistence half:
AbstractPersistentObject for entities and
AbstractPersistentOperation for operations.
The application's generated persistence classes extend these, the Wurbelizer fills in the entity-specific mapping
code (see persistence-wurblets),
and at runtime the PDO proxy delegates all persistence calls here.
Where tentackle-database knows about tables, columns, PreparedStatements, and
result sets (its AbstractDbObject/AbstractDbOperation), this module raises that to the level of domain
objects: it understands aggregates and their components, eager joins, the modification tracker, token locks and
the domain context. It is therefore the topmost runtime layer of the persistence stack — everything above it
(domain logic, UI) deals only in PDOs.
Design principles¶
- Bridge, not a new model. The module does not invent its own entity model; it implements the
PersistentObject/PersistentOperationcontracts fromtentackle-pdoon top of theAbstractDbObject/AbstractDbOperationprimitives fromtentackle-database. - Generated, not handwritten, mappings. The per-entity SQL and column mapping live in generated code woven into the application's persistence classes by the Wurbelizer; the handwritten code here is the generic engine those classes plug into.
- Aggregate-aware. Roots and their components are first-class: operations like
insertPlainWithComponents/deletePlainWithComponentsand eagerJoins understand composite relations so DDD invariants are preserved. - Location transparency via TRIP. Every persistence capability has a remote delegate counterpart in the
*.trippackages, so the identical PDO code runs whether the database is local or reached through a Tentackle server, which in turn may itself be a client of another Tentackle server, and so on. - SPI-replaced infrastructure. Several singletons (
DbUtilities,ModificationTracker,LockManager,DbClassVariablesFactory, the remote-session/delegate factories) are replaced with PDO-aware implementations through Tentackle's@Service/ServiceFindermechanism, so the lower layers transparently become PDO-aware.
Key Concepts¶
AbstractPersistentObject¶
AbstractPersistentObject<T,P> is the heart of the
module — the base persistence implementation of an entity PDO. It extends
AbstractDbObject<P> (database layer) and implements PersistentObject<T> (PDO contract), where T is the PDO
interface and P the concrete persistence class. It is @TripReferencable(NEVER) because only the PDO proxy
can be referenced when it travels across the wire, never the implementation.
Its (large) API covers the full persistence lifecycle of a domain object:
| Group | Examples | Purpose |
|---|---|---|
| Single-object CRUD | select(id), selectForUpdate, selectTokenLocked, insertObject, updateObject, deleteObject, save, delete, reload |
Read and write one entity, optionally locking it. |
| Bulk / aggregate | insertPlainWithComponents, deletePlainWithComponents, selectAll, selectAny(ids), save(Collection,…) |
Operate on collections and on roots together with their components. |
| Querying | selectByTemplate, selectByNormText, findDuplicate, selectLatest, selectAllAsCursor, selectByNormTextAsCursor |
Higher-level finders, including streaming via cursors. |
| Caching | selectCached, selectCachedOnly, selectAllCached, selectAnyCached, selectForCache, expireCache |
The PDO cache, kept coherent via the modification tracker's serials. |
| Modification / serials | countModification, selectAllWithExpiredTableSerials, updateRootContext |
Hook into change tracking and table serials. |
| Token locking | the editedBy / editedSince / editedExpiry token-lock attributes plus the lock-aware select*/reload* variants |
Cooperative, persisted "currently edited by user X" locks. |
| Normtext | updateNormText, selectByNormText |
Phonetically normalized search text (see NormText). |
An entity optionally also carries a normText column (a phonetically normalized search string) and the token-lock columns, which is why those concerns are baked into the base class.
AbstractPersistentOperation¶
AbstractPersistentOperation<T,P> is the
persistence base for operations — units of work that are not a single entity but still need a persistence
context, typically complex multistep transactions (see
operation.md). It extends AbstractDbOperation<P> and
implements PersistentOperation<T> / PersistenceDelegate<T>, is bound to a DomainContext, and is remoting
capable like entities.
Class variables¶
Tentackle keeps per-class (static) metadata in class-variable objects.
PersistentObjectClassVariables and
PersistentOperationClassVariables extend
the database layer's DbObjectClassVariables / DbOperationClassVariables with PDO-level information. They are
produced by PersistentClassVariablesFactory,
which is registered as a @Service(DbClassVariablesFactory.class) so it replaces and extends the database
layer's factory — making all class variables PDO-aware.
Modification tracking¶
PdoModificationTracker is the persistence-layer
implementation of the ModificationTracker from tentackle-session
(registered via @Service(ModificationTracker.class)). It maintains the table/modification serials that drive
cache expiry and cross-client change notification: when an entity is written, its serial advances; caches and
remote clients compare serials to learn what to refresh.
Token locking¶
A token lock is a cooperative, persisted lock expressing "this object is currently being edited by user X until time Y". Unlike a database row lock, it survives across transactions and is visible to other users.
TokenLock— the interface for an object that can be token-locked (backed by theeditedBy/editedSince/editedExpirycolumns on every PDO).LockManager— tracks the state of all token locks in parallel to the PDO columns, so the full set of locks is known without scanning every table. It exists to clean up locks when a client session crashes (drop that user's locks) or when the server restarts (drop all locks). It is only enabled on non-remote servers; in 2-tier and remote-client setups it is disabled.DefaultLockManager(@Service(LockManager.class)) persists locks in a dedicated table ofDbTokenLockrows.
Joins¶
Join and JoinedSelect
implement PDO-level joins — fetching two related PDOs in a single SELECT. They back both eager joins
(relations marked to load with their root) and explicit domain-driven joins, and work for composite
(aggregate component) and non-composite relations alike.
Cursors¶
ResultSetCursor is a ScrollableResource<T> that turns a
ResultSetWrapper into a forward/scrollable stream of PDOs, so large result sets can be processed without
materializing them all in memory. Its remote counterpart is
RemoteResultSetCursor /
RemoteResultSetCursorImpl, so cursors work
over a remote connection too.
NormText¶
NormText builds a normtext — a phonetically normalized string —
from user input, for searching PDO normtext columns with LIKE/NOT LIKE. Every persistent object maintains such
a column, enabling tolerant, accent/case-insensitive searches.
Utilities¶
PersistenceUtilities is registered as
@Service(DbUtilities.class) and replaces the database layer's DbUtilities with a PDO-aware version — the
single most visible example of how this module quietly upgrades the layer beneath it.
Remoting (TRIP)¶
Persistence is fully location transparent: the same PDO API runs against a local database or a remote
Tentackle server. This is achieved by pairing each persistence capability with a TRIP remote delegate — an
interface (the contract) plus an …Impl (the server-side executor):
| Delegate (interface / impl) | Mirrors |
|---|---|
AbstractPersistentObjectRemoteDelegate / …Impl |
AbstractPersistentObject (entity CRUD/queries) |
AbstractPersistentOperationRemoteDelegate / …Impl |
AbstractPersistentOperation |
AdminExtensionAdapterRemoteDelegate / …Impl |
admin/session-management operations |
RemoteResultSetCursor / …Impl |
streaming cursors |
PdoRemoteDelegateLocator
(@Service(RemoteDbDelegateLocator.class)) locates the right delegate for a PDO. On the client, a PDO method call
is routed to the matching delegate; on the server, the …Impl runs the real AbstractPersistentObject logic
against a local database connection and ships the results back via TRIP.
Session-side remoting is provided by
PdoRemoteSessionAdapter (a PDO-aware
RemoteSessionAdapter) and PdoRemoteSessionFactory
(@Service(RemoteSessionFactory.class)), while
DefaultPersistenceDelegateLinker
(@Service(PersistenceDelegateLinker.class)) wires a PDO proxy to its persistence delegate, and
AdminExtensionAdapter
(@Service(AdminExtension.class)) adds administrative session services.
Built-in Persistence Implementations¶
The module ships ready-made persistence implementations for the framework's own infrastructure PDOs. Each is
annotated @PersistentObjectService(...) so it is discovered as the persistence half of the corresponding PDO:
- Number sources (
org.tentackle.persist.ns) — server-side allocation of unique IDs / number ranges:NumberPoolPersistenceImplandNumberRangePersistenceImpl, with their remote delegates underns/trip. - Security rules (
org.tentackle.persist.security) —SecurityPersistenceImplpersists the access-control rules, with its remote delegate undersecurity/trip.
Server Application Lifecycle¶
The org.tentackle.persist.app package provides the entry points for the middle-tier server:
AbstractServerApplication— base class handling server startup/shutdown, configuration and the persistence environment.ServerApplication— the concrete TRIP application server clients connect to; it hosts the remote delegates described above and runs the (enabled)LockManagerandModificationTracker.
Package Layout¶
| Package | Contents |
|---|---|
org.tentackle.persist |
The core: AbstractPersistentObject, AbstractPersistentOperation, class-variables and their factory, the modification tracker, token-lock interfaces, joins, cursors, normtext, the PDO-aware utilities and the session/delegate adapters. |
org.tentackle.persist.app |
Server-application lifecycle (AbstractServerApplication, ServerApplication). |
org.tentackle.persist.lock |
The default LockManager and its persisted DbTokenLock. |
org.tentackle.persist.ns (+ ns.trip) |
Persistence for number sources (pools/ranges) and their remoting. |
org.tentackle.persist.security (+ security.trip) |
Persistence for security rules and their remoting. |
org.tentackle.persist.trip |
The PDO-aware TRIP remote delegates (entity, operation, admin, cursor) and the delegate locator. |
org.tentackle.persist.service |
The Hook (ModuleHook) for i18n bundle resolution. |
How It Fits Together¶
A typical read of an entity through a PDO proxy flows like this:
PDO proxy (Customer)
│ select(id)
▼
persistence delegate ── AbstractPersistentObject.select(id)
│
local ──────────────┐ remote ─────────────┐
▼ │ ▼ │
AbstractDbObject │ builds SQL via PdoRemoteDelegateLocator
+ Backend (SQL) │ the Backend → …RemoteDelegate (TRIP)
▼ │ ▼ (on server)
JDBC ResultSet ─────┘ …RemoteDelegateImpl runs the
│ *same* AbstractPersistentObject
▼ logic against a local connection
mapped PDO(s) returned
For a local session, AbstractPersistentObject assembles SQL via the
Backend and executes it through the
tentackle-database primitives. For a remote session, the call is shipped to
the server, where the corresponding …RemoteDelegateImpl runs the very same logic against its local connection and
returns the PDOs over TRIP. Caches stay coherent because every write advances a serial in the
PdoModificationTracker, which clients consult to expire stale entries.
The per-entity SQL, column mapping, finders, and relations are generated into the application's persistence classes by the Wurbelizer using the model; this module is the generic runtime those generated classes extend. See persistence-wurblets for the generation side and model-definition for where entities, relations, and attributes are declared.
Module Dependencies¶
tentackle-persistence sits at the top of the persistence stack:
- It requires transitive
tentackle-pdo(the PDO contracts it implements) andtentackle-database(the low-level DB engine it builds on), plusjava.naming. - It opens its
persist,persist.ns,persist.securityandpersist.trippackages toorg.tentackle.corefor TRIP serialization. - It provides a
ModuleHookservice (org.tentackle.persist.service.Hook) for i18n. - Through
@Service/@MappedServiceit replaces several lower-layer singletons with PDO-aware versions (DbUtilities→PersistenceUtilities,ModificationTracker→PdoModificationTracker,DbClassVariablesFactory→PersistentClassVariablesFactory, the remote-session/delegate factories), so code written against the database layer automatically becomes PDO-aware at runtime.
Related Documentation¶
- PDO — the persistent domain object pattern this module implements.
- Operations — operations backed by
AbstractPersistentOperation. - Persistence Wurblets — generates the per-entity persistence code that extends this module.
- Model Definition — where entities, relations, and attributes are declared.
- Session — the session/transaction/tracking context the persistence layer runs in.
- Tentackle SQL — the dialect-aware backend that builds the executed SQL.
- Services / ServiceFinder — the SPI mechanism behind the
@Service-replaced singletons and delegate discovery.