javers-exposed
Latest stable Based on Javers release 0.2.1
javers-exposed stores JaVers commit metadata and encoded CDO snapshots in relational tables through Exposed JDBC. Choose it when audit history must survive service restarts and the team already operates a relational database.
Dependency and schema
Section titled “Dependency and schema”dependencies { implementation("io.github.bluetape4k.javers:javers-exposed") runtimeOnly("org.postgresql:postgresql")}ExposedCdoSnapshotRepository is the module’s primary API. CdoSnapshotTable maps to javers_snapshot with a unique (global_id, version) index. CommitTable maps to javers_commit and stores author, timestamps, properties, and the repository-local sequence used to restore the head. See JaversExposedTables.kt.
Runnable quick start: initialize and register
Section titled “Runnable quick start: initialize and register”import io.bluetape4k.javers.persistence.exposed.repository.ExposedCdoSnapshotRepositoryimport org.javers.core.JaversBuilderimport org.javers.core.metamodel.annotation.Idimport org.jetbrains.exposed.v1.jdbc.Database
data class Order(@Id val id: Long, val status: String)
val database = Database.connect( url = "jdbc:postgresql://localhost:5432/app", driver = "org.postgresql.Driver", user = "app", password = "secret",)
val order = Order(1, "PLACED")val repository = ExposedCdoSnapshotRepository(database)repository.ensureSchema()
val javers = JaversBuilder.javers() .registerJaversRepository(repository) .registerEntity(Order::class.java) .build()
javers.commit("order-service", order)ensureSchema() invokes SchemaUtils.create(CommitTable, CdoSnapshotTable). It is useful for tests and local startup. Production should version the same schema with the service’s migration process and run the application with DML-only permissions where possible. The implementation is pinned in ExposedCdoSnapshotRepository.kt.
Persistence and query semantics
Section titled “Persistence and query semantics”Every repository operation runs in transaction(database) or the current default Exposed transaction. saveSnapshot inserts commit metadata if absent and then inserts one encoded snapshot. The inherited persist loop saves snapshots one by one and updates the commit sequence afterward in another transaction. Domain data, every audit snapshot, and the head sequence are therefore not one database transaction in 0.2.1.
Snapshots for one GlobalId are returned by descending version. Broader JaVers queries inherit the core repository’s getAll() filtering and do not push filters into SQL. Plan indexes and retention for the current schema, but do not assume that an arbitrary JQL query becomes a bounded SQL query.
Failure modes and operations
Section titled “Failure modes and operations”The unique index rejects the same GlobalId/version twice, but does not make a whole JaVers commit idempotent. A failure can leave domain state, commit metadata, or earlier snapshots without the final sequence update. Reconcile by GlobalId, version, and commit ID; use an outbox or explicit orchestration if another resource must move with the audit write.
Monitor transaction failures, duplicate-key errors, row growth, large payloads, query latency, and a head that cannot be restored. Back up both tables together. A codec change must preserve the ability to decode existing state values.
Testing
Section titled “Testing”./gradlew :javers-exposed:testExposedCdoSnapshotRepositoryH2Test.kt verifies schema use, newest-first history, query filtering, snapshot lookup, and head restoration. Run database smoke tests against the same vendor and migration used in production.
Non-goals
Section titled “Non-goals”- It does not persist application domain objects.
- It does not make application and audit transactions atomic.
- It does not provide SQL pushdown for general JaVers queries.
ensureSchema()is not a migration ledger.
Related reading: Exposed persistence, failure contracts, and testing.