PostgresSnapshotStore

class PostgresSnapshotStore(dataSource: DataSource, context: String, dispatcher: CoroutineDispatcher = VirtualThreads.dispatcher, ddl: Ddl = Ddl.AUTO) : SnapshotStore, SnapshotRebuildStore

The SnapshotStore of one bounded context on PostgreSQL (documentation/designs/03-log-and-snapshot.md §3).

Each aggregate type has a table of its own (PostgresSnapshotTables), created on its first save or load, which claims its format version first (PostgresFormat). One table holds one row per aggregate. The state and the access attributes are jsonb, so the snapshot can be queried in place as the default read model. A save is one autocommitted statement that carries its own version and state revision guard (see PostgresSnapshotTables.save); a save refused for a higher state revision then marks the row stale. The claimed state revision of each table is a row of PostgresSnapshotTables.TABLES, claimed with one statement.

JDBC calls run on dispatcher, virtual threads by default (JDK 21). A failure of the database is a defect and is thrown: a snapshot is a cache, so the caller may drop it and rebuild later.

Constructors

Link copied to clipboard
constructor(dataSource: DataSource, context: String, dispatcher: CoroutineDispatcher = VirtualThreads.dispatcher, ddl: Ddl = Ddl.AUTO)

Properties

Link copied to clipboard
open override val rebuilds: SnapshotRebuildStore

This store rewrites its snapshots in place: it is its own rebuild control plane.

Functions

Link copied to clipboard
open suspend override fun claim(type: AggregateType, revision: Int, lower: Boolean): SnapshotTable
Link copied to clipboard

Nothing to create up front: each aggregate type's table is created on its first use (createTable), since the store does not know the context's aggregate types. Kept for the host, which calls it at startup.

Link copied to clipboard
fun createTable(aggregateName: String)

Creates the table of aggregateName when missing, once per store; refuses snapshots recorded in another format version (PostgresFormat).

Link copied to clipboard
open suspend override fun load(ref: AggregateRef): SnapshotRecord?
Link copied to clipboard
open suspend override fun mark(type: AggregateType, revision: Int, rebuilding: Boolean?, backfilled: Boolean?): SnapshotTable?
Link copied to clipboard
open suspend override fun markStale(ref: AggregateRef): Boolean
Link copied to clipboard
open suspend override fun pending(type: AggregateType, revision: Int, after: AggregateRef?, limit: Int): List<AggregateRef>
Link copied to clipboard
open suspend override fun rewrite(snapshot: SnapshotRecord): Boolean
Link copied to clipboard
open suspend override fun save(snapshot: SnapshotRecord): Boolean
Link copied to clipboard
open suspend override fun saveAll(snapshots: List<SnapshotRecord>): List<SnapshotSaveOutcome>

Saves snapshots: in the rounds SnapshotSaveOutcome.rounds gives, each round's snapshots of one aggregate type with one statement (PostgresSnapshotTables.saveRows, at most PostgresSnapshotTables.MAX_ROWS a statement), then, only when some were refused, one statement marking those of a higher state revision stale. So a batch of one aggregate type that all lands is one round trip, as one save is.

Link copied to clipboard
open suspend override fun table(type: AggregateType): SnapshotTable?